Où le mot clé “virtuel” est-il nécessaire dans une hiérarchie complexe d’inheritance multiple?

Je comprends les bases de l’inheritance virtuel C ++. Cependant, je ne comprends pas exactement où il me faut utiliser le mot clé virtual avec une hiérarchie de classes complexe. Par exemple, supposons que j’ai les classes suivantes:

  A / \ BC / \ / \ DEF \ / \ / GH \ / I 

Si je veux m’assurer qu’aucune des classes n’apparaît plus d’une fois dans aucune des sous-classes, quelles classes de base doivent être marquées comme virtual ? Tous? Ou est-il suffisant de l’utiliser uniquement sur les classes qui dérivent directement d’une classe pouvant autrement avoir plusieurs instances (par exemple, B, C, D, E et F; et G et H (mais uniquement avec la classe de base E, pas avec les classes de base D et F))?

Vous devez spécifier virtual inheritance virtual lorsque vous héritez de l’une des classes A, B, C et E (situées au sumt d’un losange).

 class A; class B: virtual A; class C: virtual A; class D: virtual B; class E: virtual B, virtual C; class F: virtual C; class G: D, virtual E; class H: virtual E, F; class I: G, H; 

J’ai joué ensemble un programme qui pourrait vous aider à étudier les subtilités des bases virtuelles. Il imprime la hiérarchie de classe sous I tant que digraphe approprié pour graphiviz ( http://www.graphviz.org/ ). Il existe un compteur pour chaque instance qui vous aide également à comprendre l’ordre de construction. Voici le programme:

 #include  int counter=0; #define CONN2(N,X,Y)\ int id; N() { id=counter++; }\ void conn() \ {\ printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \ printf("%s_%d->%s_%d\n",#N,this->id,#Y,((Y*)this)->id); \ X::conn(); \ Y::conn();\ } #define CONN1(N,X)\ int id; N() { id=counter++; }\ void conn() \ {\ printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \ X::conn(); \ } struct A { int id; A() { id=counter++; } void conn() {} }; struct B : A { CONN1(B,A) }; struct C : A { CONN1(C,A) }; struct D : B { CONN1(D,B) }; struct E : B,C { CONN2(E,B,C) }; struct F : C { CONN1(F,C) }; struct G : D,E { CONN2(G,D,E) }; struct H : E,F { CONN2(H,E,F) }; struct I : G,H { CONN2(I,G,H) }; int main() { printf("digraph inh {\n"); I i; i.conn(); printf("}\n"); } 

Si je lance ceci ( g++ base.cc ; ./a.out >h.dot ; dot -Tpng -o o.png h.dot ; display o.png ), j’obtiens l’arborescence de base non virtuelle typique: alt text http://soffr.miximages.com/c%2B%2B/2ns6pt4.png

Ajouter assez de virtualité …

 struct B : virtual A { CONN1(B,A) }; struct C : virtual A { CONN1(C,A) }; struct D : virtual B { CONN1(D,B) }; struct E : virtual B, virtual C { CONN2(E,B,C) }; struct F : virtual C { CONN1(F,C) }; struct G : D, virtual E { CONN2(G,D,E) }; struct H : virtual E,F { CONN2(H,E,F) }; struct I : G,H { CONN2(I,G,H) }; 

..résultats en forme de losange (regardez les chiffres pour connaître l’ordre de construction !!)

texte alternatif http://soffr.miximages.com/c%2B%2B/xpa2l5.png

Mais si vous rendez toutes les bases virtuelles:

 struct A { int id; A() { id=counter++; } void conn() {} }; struct B : virtual A { CONN1(B,A) }; struct C : virtual A { CONN1(C,A) }; struct D : virtual B { CONN1(D,B) }; struct E : virtual B, virtual C { CONN2(E,B,C) }; struct F : virtual C { CONN1(F,C) }; struct G : virtual D, virtual E { CONN2(G,D,E) }; struct H : virtual E, virtual F { CONN2(H,E,F) }; struct I : virtual G,virtual H { CONN2(I,G,H) }; 

Vous obtenez un diamant avec un ordre d’initialisation différent :

texte alt http://soffr.miximages.com/c%2B%2B/110dlj8.png

S’amuser!

Ma suggestion personnelle serait de commencer par B et C: virtuel A, puis de continuer à append jusqu’à ce que le compilateur cesse de se plaindre.

En réalité, je dirais que B et C: virtuel A, G et H: virtuel E et E: virtuel B et C. Tous les autres liens d’inheritance peuvent être un inheritance normal. Cette monstruosité prendrait environ six décennies pour faire un appel virtuel, cependant.

Si vous voulez vous assurer qu’un object de la classe supérieure de la hiérarchie ( I dans votre cas) contient exactement un sous-object de chaque classe parente, vous devez rechercher toutes les classes de votre hiérarchie comportant plusieurs superclasses et les rendre bases virtuelles de leurs super-classes. C’est tout.

Dans votre cas, les classes A , B , C et E doivent devenir des classes de base virtuelles chaque fois que vous en héritez dans cette hiérarchie.

Les classes D , F , G et H ne doivent pas devenir des classes de base virtuelles.

Si vous voulez avoir une seule instance “physique” de chaque type pour chaque instance de chaque type (un seul A, un seul B, etc.), vous devrez simplement utiliser l’inheritance virtuel à chaque fois que vous utiliserez l’inheritance.

Si vous souhaitez séparer des instances de l’un des types, utilisez l’inheritance normal.

Edité : Je pensais que A était la classe la plus dérivée;)

La réponse de @ Luther est vraiment cool, mais revenons à la question initiale:

Vous devez utiliser virtual inheritance virtual lorsque vous héritez d’une classe dont au moins une autre classe hérite dans la hiérarchie d’inheritance (dans les diagrammes de Luther, cela signifie qu’au moins deux flèches pointent vers la classe).

Ici, il est inutile avant D , F , G et H car une seule classe en dérive (et aucune ne dérive de I pour le moment).

Cependant, si vous ne savez pas au préalable si une autre classe héritera ou non de votre classe de base, vous pouvez append virtual par précaution. Par exemple, il est recommandé à une classe Exception d’hériter virtuellement de std::exception par Stroustrup lui-même.

Comme Luther l’a noté, cela modifie l’ordre d’instanciation (et a un léger effet sur les performances), mais j’envisagerais au départ toute conception reposant sur l’ordre de construction. Et à titre de précision: vous avez toujours la garantie que les classes de base sont initialisées avant tout atsortingbut de la classe dérivée, et donc avant l’exécution du corps du constructeur de celle-ci.

Il faut garder à l’esprit que C ++ garde un tableau de l’inheritance. Plus vous ajoutez de classes virtuelles, plus le temps de compilation (liaison) est long et plus le temps d’exécution est long.

En général, si l’on peut éviter les classes virtuelles, vous pouvez remplacer par certains modèles ou essayer de découpler d’une manière ou d’une autre.