web-dev-qa-db-fra.com

Héritage multiple de deux classes dérivées

J'ai une classe de base abstraite qui agit comme une interface.

J'ai deux "ensembles" de classes dérivées, qui implémentent la moitié de la classe abstraite. (Un "ensemble" définit les méthodes virtuelles abstraites liées à l'initialisation, l'autre "ensemble" définit celles liées au "travail" réel.)

J'ai ensuite des classes dérivées qui utilisent l'héritage multiple pour construire des classes entièrement définies (et n'ajoutent rien lui-même).

Donc: (mauvais pseudocode)

class AbsBase {
  virtual void init() = 0;
  virtual void work() = 0;
}

class AbsInit : public AbsBase {
  void init() { do_this(); }
  // work() still abs
}

class AbsWork : public AbsBase {
  void work() { do_this(); }
  // init() still abs
}

class NotAbsTotal : public AbsInit, public AbsWork {
  // Nothing, both should be defined
}

Tout d'abord, puis-je faire cela? Puis-je hériter de deux classes dérivées de la même base? (J'espere).

Voici le "vrai problème" (j'ai menti un peu plus haut pour simplifier l'exemple).

Ce que j'ai vraiment fait, c'est d'ajouter des méthodes d'accesseurs non abstraits à la classe de base:

class AbsBase {
public:
  void init() { init_impl(); }
  void work() { work_impl(); }

private:
  virtual void init_impl() = 0;
  virtual void work_impl() = 0;
}

Car, un idiome commun est de rendre toutes les méthodes virtuelles privées.

Malheureusement, maintenant AbsInit et AbsWork héritent de ces méthodes, et donc NotAbsTotal hérite de "deux de chaque" (je me rends compte que je suis peut-être en train de massacrer ce qui se passe réellement au moment de la compilation).

Quoi qu'il en soit, g ++ se plaint que: "la demande du membre init () est ambiguë" lors de l'utilisation de la classe.

Je suppose que si j'avais utilisé ma classe AbsBase comme une interface pure, cela aurait été évité (en supposant que l'exemple du haut est valide).

Alors: - Suis-je loin de mon implémentation? - Est-ce une limitation de l'idiome de rendre les méthodes virtuelles privées? - Comment refactoriser mon code pour faire ce que je veux? (Fournir une interface commune, mais permettre un moyen d'échanger des implémentations pour des "ensembles" de fonctions membres)

Éditer:

Semble que je ne suis pas le premier: http://en.wikipedia.org/wiki/Diamond_problem

Semble l'héritage virtuel est la solution ici. J'ai déjà entendu parler d'héritage virtuel, mais je n'ai pas tourné la tête autour de lui. Je suis toujours ouvert aux suggestions.

26
mmocny

Il semble que vous souhaitiez effectuer un héritage virtuel. Que cela se révèle être une bonne idée est une autre question, mais voici comment procéder:


class AbsBase {...};
class AbsInit: public virtual AbsBase {...};
class AbsWork: public virtual AbsBase {...};
class NotAbsTotal: public AbsInit, public AbsWork {...};

Fondamentalement, l'héritage multiple non virtuel par défaut inclura une copie de chaque classe de base dans la classe dérivée et inclut toutes leurs méthodes. C'est pourquoi vous avez deux copies d'AbsBase - et la raison pour laquelle votre méthode est ambiguë est que les deux ensembles de méthodes sont chargés, donc C++ n'a aucun moyen de savoir à quelle copie accéder!

L'héritage virtuel condense toutes les références à une classe de base virtuelle en une seule infrastructure de données. Cela devrait rendre les méthodes de la classe de base sans ambiguïté. Cependant, notez: s'il y a des données supplémentaires dans les deux classes intermédiaires, il peut y avoir une petite surcharge supplémentaire d'exécution, pour permettre au code de trouver la classe de base virtuelle partagée.

35
comingstorm

Cela peut être fait, bien qu'il donne le plus de frissons.

Vous devez utiliser "l'héritage virtuel", dont la syntaxe est quelque chose comme

class AbsInit: public virtual AbsBase {...};
class AbsWork: public virtual AbsBase {...};
class NotAbsTotal: public AbsInit, public AbsWork {...};

Ensuite, vous devez spécifier la fonction que vous souhaitez utiliser:

NotAbsTotal::work()
{
    AbsInit::work_impl();
}

(MISE À JOUR avec la syntaxe correcte)

1
James Curran

Vous devez déclarer l'héritage comme virtuel:

struct AbsBase {
          virtual void init() = 0;
          virtual void work() = 0;
};

struct AbsInit : virtual public AbsBase {
          void init() {  }
};

struct AbsWork : virtual public AbsBase {
          void work() { }
};

struct NotAbsTotal : virtual public AbsInit, virtual public AbsWork {
};

void f(NotAbsTotal *p)
{
        p->init();
}

NotAbsTotal x;
1
Martin v. Löwis

Vous devez commencer à penser en termes de ce que vous essayez de modéliser ici.

L'héritage public ne doit être utilisé que pour modéliser une relation "isa", par ex. un chien est un animal, un carré est une forme, etc.

Jetez un œil au livre de Scott Meyer Effective C++ pour un excellent essai sur ce que les différents aspects de la conception OO ne doivent être interprétés que comme.

Edit: j'ai oublié de dire que bien que les réponses fournies jusqu'à présent soient techniquement correctes, je ne pense pas que l'une d'entre elles aborde les questions de ce que vous essayez de modéliser et c'est le noeud de votre problème!

HTH

à votre santé,

Rob

0
Rob Wells

J'ai trouvé un bon et simple exemple sur le lien ci-dessous. L'article explique avec un exemple de programme pour calculer l'aire et le périmètre d'un rectangle. Vous pouvez le vérifier .. cheers

L'héritage à plusieurs niveaux est une hiérarchie d'héritage dans laquelle une classe dérivée hérite de plusieurs classes de base. Lire la suite..

http://www.mobihackman.in/2013/09/multiple-inheritance-example.html

0
Techman92