Question

class A                     { public: void eat(){ cout<<"A";} }; 
class B: virtual public A   { public: void eat(){ cout<<"B";} }; 
class C: virtual public A   { public: void eat(){ cout<<"C";} }; 
class D: public         B,C { public: void eat(){ cout<<"D";} }; 

int main(){ 
    A *a = new D(); 
    a->eat(); 
} 

Je comprends le problème de diamant, et au-dessus morceau de code n'a pas ce problème.

Comment exactement l'héritage virtuel résoudre le problème?

Ce que je comprends: Quand je dis A *a = new D();, le compilateur veut savoir si un objet de type D peut être affecté à un pointeur de type A, mais il a deux voies qu'il peut suivre, mais ne peut pas décider par lui-même.

Alors, comment résoudre de l'héritage virtuel la question (compilateur aider à prendre la décision)?

Était-ce utile?

La solution

Vous voulez: (avec l'héritage virtuel Achievable)

  A  
 / \  
B   C  
 \ /  
  D 

Et pas: (Ce qui se passe sans héritage virtuel)

A   A  
|   |
B   C  
 \ /  
  D 

moyen d'héritage virtuel qu'il y aura seulement 1 instance de la classe de base A pas 2.

Votre type D aurait 2 pointeurs vtable (vous pouvez les voir dans le premier schéma), un pour B et un pour C qui A de pratiquement Hériter. La taille de l'objet de D est augmentée car il stocke 2 pointeurs maintenant; mais il n'y a qu'un seul A maintenant.

B::A et C::A sont les mêmes et donc il ne peut y avoir des appels ambigus de D. Si vous n'utilisez pas l'héritage virtuel que vous avez le deuxième schéma ci-dessus. Et tout appel à un membre de A devient alors ambiguë et vous devez spécifier le chemin que vous voulez prendre.

Wikipédia a un autre bon exemple ici et délabrée

Autres conseils

Les instances de classes dérivées "contiennent" des instances de classes de base, donc ils regardent en mémoire comme ça:

class A: [A fields]
class B: [A fields | B fields]
class C: [A fields | C fields]

Ainsi, sans héritage virtuel, instance de la classe D ressemblerait à ceci:

class D: [A fields | B fields | A fields | C fields | D fields]
          '- derived from B -' '- derived from C -'

, note deux "copies" de données. A des moyens d'héritage virtuel à l'intérieur de la classe dérivée il y a un ensemble de pointeur vtable lors de l'exécution que les points de données de classe de base, de sorte que les cas de B, les classes C et D ressemblent à:

class B: [A fields | B fields]
          ^---------- pointer to A

class C: [A fields | C fields]
          ^---------- pointer to A

class D: [A fields | B fields | C fields | D fields]
          ^---------- pointer to B::A
          ^--------------------- pointer to C::A

Pourquoi une autre réponse?

Eh bien, beaucoup de messages sur SO et des articles dire à l'extérieur, ce problème de diamant est résolu par la création d'instance unique de A lieu de deux (un pour chaque parent de D), résolvant ainsi l'ambiguïté. Cependant, cela ne m'a pas donné la compréhension complète du processus, je me suis retrouvé avec encore plus de questions comme

  1. si essaie de B et C pour créer différentes instances de A par exemple L'appel constructeur paramétrés avec des paramètres différents (D::D(int x, int y): C(x), B(y) {})? Quelle instance de A seront choisis pour faire partie de D?
  2. si j'utilise l'héritage non virtuel pour B, mais un virtuel pour C? Est-ce suffisant pour créer seule instance de A dans D?
  3. dois-je utiliser toujours l'héritage virtuel par défaut à partir de maintenant comme mesure préventive, car il résout le problème de diamant possible avec un coût mineur de performance et pas d'autres inconvénients?

Ne pas être en mesure de prédire le comportement sans essayer des moyens d'exemples de code qui ne comprennent pas le concept. Voici ce que m'a aidé à envelopper la tête autour héritage virtuel.

Double A

Tout d'abord, permet de commencer par ce code sans héritage virtuel:

#include<iostream>
using namespace std;
class A {
public:
    A()                { cout << "A::A() "; }
    A(int x) : m_x(x)  { cout << "A::A(" << x << ") "; }
    int getX() const   { return m_x; }
private:
    int m_x = 42;
};

class B : public A {
public:
    B(int x):A(x)   { cout << "B::B(" << x << ") "; }
};

class C : public A {
public:
    C(int x):A(x) { cout << "C::C(" << x << ") "; }
};

class D : public C, public B  {
public:
    D(int x, int y): C(x), B(y)   {
        cout << "D::D(" << x << ", " << y << ") "; }
};

int main()  {
    cout << "Create b(2): " << endl;
    B b(2); cout << endl << endl;

    cout << "Create c(3): " << endl;
    C c(3); cout << endl << endl;

    cout << "Create d(2,3): " << endl;
    D d(2, 3); cout << endl << endl;

    // error: request for member 'getX' is ambiguous
    //cout << "d.getX() = " << d.getX() << endl;

    // error: 'A' is an ambiguous base of 'D'
    //cout << "d.A::getX() = " << d.A::getX() << endl;

    cout << "d.B::getX() = " << d.B::getX() << endl;
    cout << "d.C::getX() = " << d.C::getX() << endl;
}

Permet de passer par sortie. B b(2); exécution crée A(2) comme prévu, même pour C c(3);:

Create b(2): 
A::A(2) B::B(2) 

Create c(3): 
A::A(3) C::C(3) 

D d(2, 3); a besoin à la fois B et C, chacun d'entre eux créant son propre A, nous avons donc deux A en d:

Create d(2,3): 
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3) 

C'est la raison pour d.getX() causer une erreur de compilation que le compilateur ne peut pas choisir par exemple A il doit appeler la méthode pour. Cependant, il est possible d'appeler des méthodes directement pour la classe mère choisie:

d.B::getX() = 3
d.C::getX() = 2

Virtuality

permet maintenant ajouter l'héritage virtuel. En utilisant un même échantillon de code avec les modifications suivantes:

class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...

permet de passer directement à la création de d:

Create d(2,3): 
A::A() C::C(2) B::B(3) D::D(2, 3) 

Vous pouvez voir, A est créé avec les paramètres par défaut en ignorant constructeur passé de constructeurs de B et C. Comme l'ambiguïté est parti, tous les appels à getX() retournent la même valeur:

d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42

Mais si l'on veut appeler un constructeur pour A paramétrisé? Il peut être fait en appelant explicitement qu'il du constructeur de D:

D(int x, int y, int z): A(x), C(y), B(z)

Normalement, la classe peut utiliser explicitement les constructeurs des parents directs seulement, mais il y a une exclusion pour le cas de l'héritage virtuel. La découverte de cette règle « cliqué » pour moi et interfaces virtuelles aidé à comprendre beaucoup:

Code de moyens de class B: virtual A, que toute la classe héritée de B est maintenant responsable de la création A par lui-même, puisque B ne va pas le faire automatiquement.

Avec cette déclaration à l'esprit, il est facile de répondre à toutes mes questions:

  1. Lors de la création de D ni B ni C est responsable des paramètres de A, il est totalement à D seulement.
  2. C déléguera création de A à D, mais B va créer sa propre instance de A ramenant ainsi des problèmes de diamant
  3. Définition de paramètres de classe de base en classe petit-enfant plutôt que l'enfant direct n'est pas une bonne pratique, il devrait donc être tolérée quand le problème de diamant existe et cette mesure est inévitable.

Le problème est pas le chemin le compilateur doit suivre. Le problème est le point final de ce chemin: le résultat de la distribution. En ce qui concerne les conversions de type, le chemin n'a pas d'importance, seul le résultat final fait.

Si vous utilisez l'héritage ordinaire, chaque chemin a son propre point de terminaison distinctif, ce qui signifie que le résultat de la distribution est ambiguë, ce qui est le problème.

Si vous utilisez l'héritage virtuel, vous obtenez une hiérarchie en forme de diamant: les deux chemins conduit au même point final. Dans ce cas, le problème du choix du chemin n'existe plus (ou, plus précisément, n'a plus d'importance), parce que les deux chemins mènent au même résultat. Le résultat n'est plus ambigu - qui est ce qui importe. Le chemin exact ne fonctionne pas.

En fait, l'exemple doit être comme suit:

#include <iostream>

//THE DIAMOND PROBLEM SOLVED!!!
class A                     { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; 
class B: virtual public A   { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; 
class C: virtual public A   { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; 
class D: public         B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} }; 

int main(int argc, char ** argv){
    A *a = new D(); 
    a->eat(); 
    delete a;
}

... cette façon la sortie est va être la bonne: "EAT => D"

L'héritage virtuel ne résout que la duplication du grand-père! Mais vous avez encore besoin de préciser les méthodes pour être virtuel afin d'obtenir les méthodes correctement overrided ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top