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 du diamant, et le morceau de code ci-dessus n'a pas ce problème.
Comment l'héritage virtuel résout-il exactement 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 assigné à un pointeur de type A
, mais il a deux chemins qu'il peut suivre, mais ne peut pas décider par lui-même.
Alors, comment l'héritage virtuel résout-il le problème (aide le compilateur à prendre la décision)?
B
lC
'implémentation de ou à la place? Merci!Les instances de classes dérivées "contiennent" des instances de classes de base, donc elles ressemblent à ceci en mémoire:
Ainsi, sans héritage virtuel, l'instance de la classe D ressemblerait à:
Alors, notez deux "copies" des données A. L'héritage virtuel signifie qu'à l'intérieur de la classe dérivée, il existe un pointeur vtable défini au moment de l'exécution qui pointe vers les données de la classe de base, de sorte que les instances des classes B, C et D ressemblent à:
la source
Pourquoi une autre réponse?
Eh bien, de nombreux articles sur SO et articles extérieurs disent que le problème du diamant est résolu en créant une seule instance de
A
au lieu de deux (une pour chaque parent deD
), résolvant ainsi l'ambiguïté. Cependant, cela ne m'a pas donné une compréhension complète du processus, je me suis retrouvé avec encore plus de questions commeB
et si etC
essayait de créer différentes instances,A
par exemple, d'appeler un constructeur paramétré avec des paramètres différents (D::D(int x, int y): C(x), B(y) {}
)? Quelle instance deA
sera choisie pour faire partie deD
?B
, mais virtuel pourC
? Est-ce suffisant pour créer une seule instance deA
inD
?Ne pas pouvoir prédire le comportement sans essayer des échantillons de code signifie ne pas comprendre le concept. Voici ce qui m'a aidé à comprendre l'héritage virtuel.
Double A
Tout d'abord, commençons avec ce code sans héritage virtuel:
Permet de passer par la sortie. L'exécution
B b(2);
créeA(2)
comme prévu, idem pourC c(3);
:D d(2, 3);
a besoin des deuxB
etC
, chacun d'eux créant le sienA
, nous avons donc le doubleA
ded
:C'est la raison pour laquelle
d.getX()
une erreur de compilation est provoquée car le compilateur ne peut pas choisir l'A
instance pour laquelle il doit appeler la méthode. Il est toujours possible d'appeler des méthodes directement pour la classe parent choisie:Virtualité
Ajoutons maintenant l'héritage virtuel. Utilisation du même exemple de code avec les modifications suivantes:
Passons à la création de
d
:Vous pouvez voir,
A
est créé avec le constructeur par défaut en ignorant les paramètres transmis par les constructeurs deB
etC
. Comme l'ambiguïté a disparu, tous les appels pourgetX()
renvoyer la même valeur:Mais que faire si nous voulons appeler le constructeur paramétré pour
A
? Cela peut être fait en l'appelant explicitement depuis le constructeur deD
:Normalement, la classe peut utiliser explicitement des constructeurs de parents directs uniquement, mais il existe une exclusion pour le cas d'héritage virtuel. La découverte de cette règle a «cliqué» pour moi et m'a beaucoup aidé à comprendre les interfaces virtuelles:
Le code
class B: virtual A
signifie que toute classe héritée deB
est désormais responsable de la créationA
par elle-même, car elleB
ne le fera pas automatiquement.Avec cette déclaration à l'esprit, il est facile de répondre à toutes mes questions:
D
création,B
niC
n'est responsable des paramètres deA
, c'est totalement àD
seulement.C
déléguera la création deA
àD
, maisB
créera sa propre instanceA
pour ramener ainsi le problème du diamantla source
Le problème n'est pas le chemin que 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 conversion est ambigu, ce qui est le problème.
Si vous utilisez l'héritage virtuel, vous obtenez une hiérarchie en forme de losange: les deux chemins mènent au même point de terminaison. Dans ce cas, le problème du choix du chemin n'existe plus (ou, plus précisément, n'a plus d'importance), car les deux chemins aboutissent au même résultat. Le résultat n'est plus ambigu, c'est ce qui compte. Le chemin exact ne l'est pas.
la source
En fait, l'exemple devrait être le suivant:
... de cette façon, la sortie sera la bonne: "EAT => D"
L'héritage virtuel ne résout que la duplication du grand-père! MAIS vous devez toujours spécifier les méthodes à être virtuelles afin d'obtenir les méthodes correctement remplacées ...
la source