Dans C++, les hiérarchies de classes peuvent poser le problème du diamant (diamond problem), lorsque deux sous-classes héritent d'une même classe de base, et qu'une autre classe est ensuite créée, héritant de ces deux-là. Dans ce cas, un objet de la classe dérivée contiendra deux copies indépendantes de la classe de base. Pour résoudre ce problème, C++ a implémenté l'héritage virtuel.
Si l'on utilise l'héritage multiple classique :
class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {};
L'objet D contiendra 2 copies de A : une à travers B, l'autre à travers C. Cela entraîne des ambiguïtés lors de l'accès à A::x et une consommation mémoire injustifiée.
L'héritage virtuel élimine la duplication de la classe de base. Une seule instance de la classe de base A sera utilisée par tous les descendants :
class A { public: int x; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
Maintenant, D ne contient qu'une seule copie de A, et l'ambiguïté avec l'accès à A::x est résolue.
Caractéristiques clés :
Pourquoi ne peut-on pas résoudre le problème du diamant en spécifiant explicitement le chemin via la résolution de portée ?
La résolution de portée ne résout que l'ambiguïté d'accès aux membres de la classe de base, mais elle ne supprime pas les copies supplémentaires de la classe de base elle-même. Le problème de double initialisation et de double stockage de données demeure.
Dans quel ordre les constructeurs sont-ils appelés lors de l'héritage virtuel ?
Les constructeurs des classes de base virtuelles sont appelés en premier et seulement une fois, directement par le constructeur de la classe la plus éloignée dans la hiérarchie d'héritage.
Exemple :
class A { public: A() { std::cout << "A\n"; } }; class B : public virtual A { public: B() { std::cout << "B\n"; } }; class C : public virtual A { public: C() { std::cout << "C\n"; } }; class D : public B, public C { public: D() { std::cout << "D\n"; } }; D d; // Sortie : A B C D
Faut-il déclarer l'héritage virtuel à la fois lors de la déclaration et de la définition de la classe ?
Oui, l'héritage virtuel doit être spécifié partout où il y a héritage de la classe de base concernée. Sinon, une erreur de compilation est inévitable, et l'héritage multiple deviendra classique.
Une hiérarchie complexe, dans laquelle l'héritage virtuel n'est pas utilisé, ce qui entraîne deux copies des paramètres dans l'objet :
Avantages :
Inconvénients :
L'héritage virtuel est utilisé avec un constructeur cohérent du descendant le plus haut :
Avantages :
Inconvénients :