Historique de la question :
C++ a hérité le concept d'héritage des classes du langage C++ et des méthodologies orientées objet. L'introduction de l'héritage multiple et virtuel a compliqué la structure des hiérarchies, augmentant la flexibilité tout en ajoutant de nouvelles classes d'erreurs.
Problème :
L'héritage direct est la situation où une classe hérite directement d'une autre sans complications supplémentaires. L'héritage indirect survient lorsque les membres hérités proviennent par une ou plusieurs chaînes intermédiaires d'héritage. La principale difficulté est le "problème du diamant" (diamond problem), où plusieurs chemins vers une même classe de base peuvent mener à la duplication de ses membres dans les classes dérivées.
Solution :
Pour gérer la complexité, l'héritage virtuel est utilisé, garantissant une seule instance commune du membre de la classe de base pour toute la hiérarchie, et non pour chaque chaîne.
Exemple de code :
class A { public: int value; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
Dans la classe D, il n'y aura qu'une seule instance du membre A::value.
Caractéristiques clés :
L'héritage virtuel peut-il provoquer un comportement indéfini s'il n'est pas utilisé en cas de problème du diamant ?
Si l'héritage virtuel n'est pas utilisé dans une hiérarchie en diamant, les membres de la classe de base seront dupliqués. Cela peut brouiller la logique du code et entraîner une ambiguïté lors de l'accès aux membres de la classe de base.
Dans quels cas l'héritage virtuel n'est-il pas nécessaire ?
Si vous êtes sûr que vos hiérarchies ne forment pas une structure en diamant, ou si la classe de base ne contient pas de données, l'héritage virtuel n'est pas requis.
Comment gérer l'appel des constructeurs lors de l'héritage virtuel ?
Le constructeur de la classe de base virtuelle est appelé uniquement par la classe dérivée "la plus basse". Dans les classes intermédiaires, il est interdit de spécifier des arguments pour la classe de base virtuelle (le constructeur est appelé par défaut s'il n'est pas initialisé explicitement dans la classe finale).
Exemple de code :
class A { public: A(int x) { /* ... */ } }; class B : public virtual A { public: B() : A(0) {} // erreur : impossible d'initialiser A ici }; class D : public B { public: D() : A(10), B() {} // correct };
Dans un grand projet, un développeur n'a pas remarqué l'apparition d'une structure en diamant — les deux classes intermédiaires étaient directement héritées d'une classe de base. L'accès au membre de la classe de base a provoqué une ambiguïté, le code était difficile à lire.
Avantages :
Inconvénients :
L'architecte a remarqué le problème à l'avance et a utilisé l'héritage virtuel pour les classes intermédiaires, et le constructeur de la classe de base virtuelle était correctement appelé uniquement dans la classe dérivée la plus basse.
Avantages :
Inconvénients :