Comment l’inheritance virtuel C ++ est-il implémenté dans les compilateurs?

Comment les compilateurs implémentent l’inheritance virtuel?

Dans le code suivant:

class A { public: A(int) {} }; class B : public virtual A { public: B() : A(1) {} }; class C : public B { public: C() : A(3), B() {} }; 

Un compilateur génère-t-il deux instances de la fonction B::ctor , une sans appel A(1) et une avec elle? Ainsi, lorsque B::constructor est appelé à partir du constructeur de la classe dérivée, la première instance est utilisée, sinon la seconde.

Cela dépend de la mise en œuvre. GCC (voir cette question ), par exemple, émettra deux constructeurs, l’un avec un appel à A(1) , l’autre sans.

 B1() B2() // no A 

Lorsque B est construit, la version “complète” est appelée:

 B1(): A(1) B() body 

Lorsque C est construit, la version de base est appelée à la place:

 C(): A(3) B2() B() body C() body 

En fait, deux constructeurs seront émis même s’il n’y a pas d’inheritance virtuel, et ils seront identiques.

Le compilateur ne crée pas un autre constructeur de B – mais ignore le A(1) . Comme A est virtuellement hérité, il est construit en premier, avec son constructeur par défaut. Et comme il est déjà construit lorsque B() est appelé, la partie A(1) est ignorée.

Edit – J’ai raté la partie A(3) liste d’initialisation du constructeur de C Lorsque l’inheritance virtuel est utilisé, seule la classe la plus dérivée initialise les classes de base virtuelles. Donc, A sera construit avec A(3) et non son constructeur par défaut. Le rest est toujours valable – toute initialisation de A par une classe intermédiaire (ici B ) est ignorée.

Éditez 2, en essayant de répondre à la question sur l’application de ce qui précède:

Dans Visual Studio (au moins 2010), un indicateur est utilisé au lieu d’avoir deux implémentations de B() . Puisque B hérite virtuellement de A , avant d’appeler le constructeur de A, l’indicateur est coché. Si l’indicateur n’est pas défini, l’appel de A() est ignoré. Ensuite, dans chaque classe dérivée de B , l’indicateur est réinitialisé après avoir initialisé A Le même mécanisme est utilisé pour empêcher C d’initialiser A si cela fait partie de D (si D hérite de C , D initialisera A ).

L’ IBI C ++ ABI est une ressource utile pour toutes les questions du type “comment cela pourrait-il être implémenté par les compilateurs C ++”.

En particulier, 5.1.4 Autres fonctions et entités spéciales répertorient différentes fonctions spéciales de membres pour différentes finalités:

  ::= C1 # complete object constructor ::= C2 # base object constructor ::= C3 # complete object allocating constructor ::= D0 # deleting destructor ::= D1 # complete object destructor ::= D2 # base object destructor 

La section 1.1 Définitions est utile (mais pas complète):

destructeur d’object de base d’une classe T

Fonction qui exécute les destructeurs pour les membres de données non statiques de T et les classes de base directe non virtuelles de T.

destructeur d’object complet d’une classe T

Fonction qui, en plus des actions requirejses d’un destructeur d’object de base, exécute les destructeurs pour les classes de base virtuelles de T.

suppression du destructeur d’une classe T

Fonction qui, en plus des actions requirejses d’un destructeur d’object complet, appelle la fonction de désallocation appropriée (c’est-à-dire, l’opérateur delete) pour T.

A partir de ces définitions, le but du constructeur d’object complet et du constructeur d’object de base est évident.

Je vous suggère de lire des papiers. Ces deux sont vraiment intéressants, surtout le premier puisqu’il provient du père de C ++:

[1] Bjarne Stroustrup. Héritage multiple pour C ++. Journal des utilisateurs de C / C ++, mai 1999.

[2] J. Templ. Une approche systématique de la mise en œuvre de l’inheritance multiple. ACM SIGPLAN Notices, volume 28, n ° 4 avril 1993.

Je les ai utilisées comme références principales lors de la préparation d’un séminaire (en tant qu’étudiant) sur le pasortingmoine multiple dans mon université.

Comme mentionné précédemment, cela dépend de l’implémentation du compilateur.

Cependant, chaque fois qu’un programmeur ajoute une nouvelle méthode, il est généralement stocké dans le code, même s’il existe une autre méthode portant le même identifiant. ailleurs (“overriden” ou “surchargé”).

Le code de chaque méthode est stocké une seule fois. Par conséquent, si une classe hérite et utilise la même méthode d’une classe parent, elle utilise en interne un pointeur sur le code, elle ne duplique pas le code.

Si une classe parent définit une méthode virtuelle et qu’une classe enfant la remplace, les deux méthodes sont stockées. Chaque classe a quelque chose appelé “Table de méthode virtuelle” où il y a une table de pointeurs pour chaque méthode.

Ne vous inquiétez pas pour les performances, le compilateur ne duplique pas le code des méthodes.