En C ++, qu'est-ce qu'une classe de base virtuelle?

403

Je veux savoir ce qu'est une " classe de base virtuelle " et ce qu'elle signifie.

Permettez-moi de vous montrer un exemple:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};
popopome
la source
devrions-nous utiliser des classes de base virtuelles dans «l'héritage multiple» parce que si la classe A a une variable membre int a et que la classe B a également un membre int a et que la classe c hérite des classes A et B, comment décidons-nous quel «a» utiliser?
Namit Sinha
2
@NamitSinha non, l'héritage virtuel ne résout pas ce problème. Le membre a serait de toute façon ambigu
Ichthyo
@NamitSinha L'héritage virtuel n'est pas un outil magique pour supprimer plusieurs ambiguïtés liées à l'héritage. Il "résout" un "problème" d'avoir une base indirecte plus d'une fois. Ce qui n'est un problème que s'il était destiné à être partagé (souvent mais pas toujours le cas).
curiousguy

Réponses:

533

Les classes de base virtuelles, utilisées dans l'héritage virtuel, sont un moyen d'empêcher que plusieurs "instances" d'une classe donnée n'apparaissent dans une hiérarchie d'héritage lors de l'utilisation de l'héritage multiple.

Considérez le scénario suivant:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

La hiérarchie des classes ci-dessus donne le "diamant redouté" qui ressemble à ceci:

  A
 / \
B   C
 \ /
  D

Une instance de D sera composée de B, qui comprend A, et C qui comprend également A. Donc, vous avez deux "instances" (à défaut d'une meilleure expression) de A.

Lorsque vous avez ce scénario, vous avez la possibilité d'ambiguïté. Que se passe-t-il lorsque vous faites cela:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

L'héritage virtuel est là pour résoudre ce problème. Lorsque vous spécifiez virtual lors de l'héritage de vos classes, vous dites au compilateur que vous ne voulez qu'une seule instance.

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

Cela signifie qu'il n'y a qu'une seule "instance" de A incluse dans la hiérarchie. Par conséquent

D d;
d.Foo(); // no longer ambiguous

Ceci est un mini résumé. Pour plus d'informations, lisez ceci et ceci . Un bon exemple est également disponible ici .

JO.
la source
7
@Bohdan non, ce n'est pas le cas :)
JO.
6
@OJ. pourquoi pas? Ils sont hilarants :)
Bohdan
15
@Bohdan utilise autant de mots-clés virtuels que moins, car lorsque nous utilisons des mots-clés virtuels, un mécanisme lourd est appliqué. Ainsi, l'efficacité de votre programme sera réduite.
Sagar
73
Votre diagramme "diamant redouté" prête à confusion, même s'il semble être couramment utilisé. Il s'agit en fait d'un diagramme montrant les relations d'héritage de classe - pas une disposition d'objet. La partie déroutante est que si nous utilisons virtual, la disposition des objets ressemble au diamant; et si nous ne l'utilisons pas, virtualla disposition de l'objet ressemble à une structure arborescente qui contient deux As
MM
5
Je dois voter contre cette réponse pour la raison décrite par MM - le diagramme exprime le contraire du message.
David Stone
251

À propos de la disposition de la mémoire

En remarque, le problème avec le diamant redouté est que la classe de base est présente plusieurs fois. Donc, avec un héritage régulier, vous pensez avoir:

  A
 / \
B   C
 \ /
  D

Mais dans la disposition de la mémoire, vous avez:

A   A
|   |
B   C
 \ /
  D

Cela explique pourquoi lors d'un appel D::foo(), vous avez un problème d'ambiguïté. Mais le vrai problème survient lorsque vous souhaitez utiliser une variable membre de A. Par exemple, disons que nous avons:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

Lorsque vous essayez d'accéder à m_iValuepartir de D, le compilateur protestera, car dans la hiérarchie, il en verra deux m_iValue, pas un. Et si vous en modifiez un, disons B::m_iValue(c'est le A::m_iValueparent de B), C::m_iValueil ne sera pas modifié (c'est le A::m_iValueparent de C).

C'est là que l'héritage virtuel est utile, car avec lui, vous reviendrez à une véritable disposition en losanges, avec non seulement une foo()méthode, mais aussi une et une seule m_iValue.

Qu'est-ce qui pourrait mal se passer?

Imaginer:

  • A a une fonctionnalité de base.
  • B y ajoute une sorte de tableau de données sympa (par exemple)
  • Clui ajoute une fonctionnalité intéressante comme un motif d'observateur (par exemple, activé m_iValue).
  • Dhérite de Bet C, et donc de A.

Avec l'héritage normal, la modification à m_iValuepartir de Dest ambiguë et cela doit être résolu. Même si c'est le cas, il y en a deux à l' m_iValuesintérieur D, il vaut donc mieux s'en souvenir et mettre à jour les deux en même temps.

Avec l'héritage virtuel, la modification m_iValuedepuis Dest ok ... Mais ... Disons que vous l'avez fait D. Grâce à son Cinterface, vous avez attaché un observateur. Et grâce à son Binterface, vous mettez à jour le tableau cool, ce qui a pour effet secondaire de changer directement m_iValue...

Comme le changement de m_iValuese fait directement (sans utiliser de méthode d'accesseur virtuel), l'observateur "écoutant" Cne sera pas appelé, car le code implémentant l'écoute est dedans C, et Bne le sait pas ...

Conclusion

Si vous avez un diamant dans votre hiérarchie, cela signifie que vous avez 95% de chances d'avoir fait quelque chose de mal avec cette hiérarchie.

paercebal
la source
Votre «ce qui pourrait mal tourner» est dû à l'accès direct à un membre de base, et non à l'héritage multiple. Débarrassez-vous de «B» et vous avez le même problème. Règle de base de: «si ce n'est pas privé, il doit être virtuel» évite le problème. M_iValue n'est pas virtuel et doit donc être privé
Chris Dodd
4
@Chris Dodd: Pas exactement. Ce qui se passe avec m_iValue serait arrivé à n'importe quel symbole ( par exemple typedef, variable membre, fonction membre, transtypé en classe de base, etc. ). Il s'agit vraiment d'un problème d'héritage multiple, un problème que les utilisateurs doivent savoir utiliser correctement l'héritage multiple, au lieu de suivre la voie Java et de conclure que "l'héritage multiple est 100% mauvais, faisons-le avec les interfaces".
paercebal
Bonjour, Lorsque nous utilisons un mot-clé virtuel, il n'y aura qu'une seule copie de A. Ma question est de savoir comment savoir s'il provient de B ou de C? Ma question est-elle valable du tout?
user875036
@ user875036: A vient à la fois de B et de C. En effet, la virtualité change quelques choses (par exemple D appellera le constructeur de A, pas B, ni C). B et C (et D) ont un pointeur vers A.
paercebal
3
FWIW, au cas où quelqu'un se poserait la question, les variables membres ne peuvent pas être virtuelles - virtual est un spécificateur de fonctions . Référence SO: stackoverflow.com/questions/3698831/…
rholmes
34

Expliquer l'héritage multiple avec des bases virtuelles nécessite une connaissance du modèle d'objet C ++. Et il est préférable d'expliquer le sujet clairement dans un article et non dans une zone de commentaire.

La meilleure explication lisible que j'ai trouvée qui a résolu tous mes doutes à ce sujet était cet article: http://www.phpcompiler.org/articles/virtualinheritance.html

Vous n'aurez vraiment pas besoin de lire autre chose sur le sujet (sauf si vous êtes un auteur de compilateur) après avoir lu cela ...

lenkite
la source
10

Une classe de base virtuelle est une classe qui ne peut pas être instanciée: vous ne pouvez pas en créer d'objet direct.

Je pense que vous confondez deux choses très différentes. L'héritage virtuel n'est pas la même chose qu'une classe abstraite. L'héritage virtuel modifie le comportement des appels de fonction; parfois, il résout les appels de fonction qui seraient autrement ambigus, parfois il reporte la gestion des appels de fonction à une classe autre que celle à laquelle on s'attend dans un héritage non virtuel.

wilhelmtell
la source
7

Je voudrais ajouter aux aimables clarifications d'OJ.

L'héritage virtuel n'a pas de prix. Comme pour tout ce qui est virtuel, vous obtenez un succès de performance. Il existe un moyen de contourner ce succès de performance qui est peut-être moins élégant.

Au lieu de casser le diamant en dérivant virtuellement, vous pouvez ajouter une autre couche au diamant, pour obtenir quelque chose comme ceci:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

Aucune des classes n'hérite virtuellement, toutes héritent publiquement. Les classes D21 et D22 masqueront alors la fonction virtuelle f () qui est ambiguë pour DD, peut-être en déclarant la fonction privée. Ils définiraient chacun une fonction wrapper, f1 () et f2 () respectivement, chacun appelant classe (privé) f (), résolvant ainsi les conflits. La classe DD appelle f1 () si elle veut D11 :: f () et f2 () si elle veut D12 :: f (). Si vous définissez les wrappers en ligne, vous n'aurez probablement aucune surcharge.

Bien sûr, si vous pouvez changer D11 et D12, vous pouvez faire la même astuce à l'intérieur de ces classes, mais ce n'est souvent pas le cas.

wilhelmtell
la source
2
Il ne s'agit pas plus ou moins d'élégance ou de résolution d'ambiguïtés (vous pouvez toujours utiliser des spécifications explicites xxx :: pour cela). Avec l'héritage non virtuel, chaque instance de la classe DD a deux instances indépendantes de B. Dès que la classe a un seul membre de données non statique, l'héritage virtuel et non virtuel diffère par plus que la simple syntaxe.
user3489112
@ user3489112 Dès que ... rien. L'héritage virtuel et non virtuel diffèrent sémantiquement, point.
curiousguy
4

En plus de ce qui a déjà été dit sur les héritages multiples et virtuels, il existe un article très intéressant sur le Journal du Dr Dobb: l' héritage multiple considéré comme utile

Luc Hermitte
la source
1

Vous êtes un peu déroutant. Je ne sais pas si vous mélangez certains concepts.

Vous n'avez pas de classe de base virtuelle dans votre OP. Vous avez juste une classe de base.

Vous avez fait l'héritage virtuel. Ceci est généralement utilisé dans l'héritage multiple afin que plusieurs classes dérivées utilisent les membres de la classe de base sans les reproduire.

Une classe de base avec une fonction virtuelle pure n'est pas instanciée. cela nécessite la syntaxe à laquelle Paul arrive. Il est généralement utilisé pour que les classes dérivées doivent définir ces fonctions.

Je ne veux plus expliquer cela parce que je ne comprends pas totalement ce que vous demandez.

Baltimark
la source
1
Une "classe de base" utilisée dans un héritage virtuel devient une "classe de base virtuelle" (dans le contexte de cet héritage précis).
Luc Hermitte
1

Cela signifie qu'un appel à une fonction virtuelle sera transféré à la "bonne" classe.

FAQ C ++ Lite FTW.

En bref, il est souvent utilisé dans des scénarios à héritage multiple, où une hiérarchie "en losange" est formée. L'héritage virtuel rompra alors l'ambiguïté créée dans la classe inférieure, lorsque vous appelez une fonction dans cette classe et que la fonction doit être résolue en classe D1 ou D2 au-dessus de cette classe inférieure. Voir l' article FAQ pour un diagramme et des détails.

Il est également utilisé dans la délégation sœur , une fonctionnalité puissante (mais pas pour les faibles de cœur). Voir cette FAQ.

Voir également l'article 40 de la 3e édition de C ++ effectif (43 dans la 2e édition).

wilhelmtell
la source
1

Exemple d'utilisation exécutable d'héritage Diamond

Cet exemple montre comment utiliser une classe de base virtuelle dans le scénario typique: pour résoudre l'héritage de diamant.

#include <cassert>

class A {
    public:
        A(){}
        A(int i) : i(i) {}
        int i;
        virtual int f() = 0;
        virtual int g() = 0;
        virtual int h() = 0;
};

class B : public virtual A {
    public:
        B(int j) : j(j) {}
        int j;
        virtual int f() { return this->i + this->j; }
};

class C : public virtual A {
    public:
        C(int k) : k(k) {}
        int k;
        virtual int g() { return this->i + this->k; }
};

class D : public B, public C {
    public:
        D(int i, int j, int k) : A(i), B(j), C(k) {}
        virtual int h() { return this->i + this->j + this->k; }
};

int main() {
    D d = D(1, 2, 4);
    assert(d.f() == 3);
    assert(d.g() == 5);
    assert(d.h() == 7);
}
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
2
assert(A::aDefault == 0);de la fonction principale me donne une erreur de compilation: en aDefault is not a member of Autilisant gcc 5.4.0. Que faut-il faire?
SebNag
@SebTu ah merci, juste quelque chose que j'ai oublié de supprimer du copier-coller, je l'ai supprimé maintenant. L'exemple devrait toujours être significatif sans lui.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Les classes virtuelles ne sont pas identiques à l'héritage virtuel. Des classes virtuelles que vous ne pouvez pas instancier, l'héritage virtuel est tout autre chose.

Wikipedia le décrit mieux que moi. http://en.wikipedia.org/wiki/Virtual_inheritance

bradtgmurray
la source
6
Il n'y a pas de "classes virtuelles" en C ++. Il existe cependant des "classes de base virtuelles" qui sont "virtuelles" concernant un héritage donné. Vous faites référence à ce que l'on appelle officiellement les "classes abstraites".
Luc Hermitte
@LucHermitte, il y a définitivement des classes virtuelles en C ++. Vérifiez ceci: en.wikipedia.org/wiki/Virtual_class .
Rafid
msgstr "erreur: 'virtuel' ne peut être spécifié que pour les fonctions". Je ne sais pas de quelle langue il s'agit. Mais il n'y a définitivement pas de classe virtuelle en C ++.
Luc Hermitte
0

Héritage régulier

Avec l'héritage d'héritage non virtuel non diamant à 3 niveaux, lorsque vous instanciez un nouvel objet le plus dérivé, new est appelé et la taille requise pour l'objet est résolue à partir du type de classe par le compilateur et passée à new.

nouveau a une signature:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)

Et fait un appel à malloc, renvoyant le pointeur vide

Il est ensuite transmis au constructeur de l'objet le plus dérivé, qui appellera immédiatement le constructeur du milieu, puis le constructeur du milieu appellera immédiatement le constructeur de base. La base stocke ensuite un pointeur sur sa table virtuelle au début de l'objet, puis ses attributs après celui-ci. Cela revient ensuite au constructeur du milieu qui stockera son pointeur de table virtuelle au même emplacement, puis ses attributs après les attributs qui auraient été stockés par le constructeur de base. Il retourne au constructeur le plus dérivé, qui stocke un pointeur sur sa table virtuelle au même emplacement, puis ses attributs après les attributs qui auraient été stockés par le constructeur du milieu.

Parce que le pointeur de table virtuelle est remplacé, le pointeur de table virtuelle finit toujours par être la classe la plus dérivée. La virtualité se propage vers la classe la plus dérivée, donc si une fonction est virtuelle dans la classe moyenne, elle sera virtuelle dans la classe la plus dérivée mais pas dans la classe de base. Si vous transformez de manière polymorphe une instance de la classe la plus dérivée en un pointeur vers la classe de base, le compilateur ne résoudra pas cela en un appel indirect à la table virtuelle et appellera à la place la fonction directement A::function(). Si une fonction est virtuelle pour le type auquel vous l'avez convertie, elle se résoudra en appel dans la table virtuelle qui sera toujours celle de la classe la plus dérivée. S'il n'est pas virtuel pour ce type, il ne fera qu'appeler Type::function()et lui passer le pointeur d'objet, transtypé en Type.

En fait, quand je dis pointeur sur sa table virtuelle, c'est en fait toujours un décalage de 16 dans la table virtuelle.

vtable for Base:
        .quad   0
        .quad   typeinfo for Base
        .quad   Base::CommonFunction()
        .quad   Base::VirtualFunction()

pointer is typically to the first function i.e. 

        mov     edx, OFFSET FLAT:vtable for Base+16

virtualn'est plus requis dans les classes plus dérivées s'il est virtuel dans une classe moins dérivée car il se propage. Mais il peut être utilisé pour montrer que la fonction est bien une fonction virtuelle, sans avoir à vérifier les classes dont elle hérite des définitions de type.

override est un autre garde du compilateur qui dit que cette fonction est en train de remplacer quelque chose et si ce n'est pas le cas, alors lancez une erreur du compilateur.

= 0 signifie qu'il s'agit d'une fonction abstraite

final empêche une fonction virtuelle d'être implémentée à nouveau dans une classe plus dérivée et s'assurera que la table virtuelle de la classe la plus dérivée contient la fonction finale de cette classe.

= default rend explicite dans la documentation que le compilateur utilisera l'implémentation par défaut

= delete donner une erreur de compilation si un appel à ceci est tenté

Héritage virtuel

Considérer

class Base
  {
      int a = 1;
      int b = 2;
  public:
      void virtual CommonFunction(){} ;
      void virtual VirtualFunction(){} ;
  };


class DerivedClass1: virtual public Base
  {
      int c = 3;
  public:
    void virtual DerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
  };

  class DerivedClass2 : virtual public Base
 {
     int d = 4;
 public:
     //void virtual DerivedCommonFunction(){} ;    
     void virtual VirtualFunction(){} ;
     void virtual DerivedCommonFunction2(){} ;
 };

class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
 {
   int e = 5;
 public:
     void virtual DerivedDerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
 };

 int main () {
   DerivedDerivedClass* d = new DerivedDerivedClass;
   d->VirtualFunction();
   d->DerivedCommonFunction();
   d->DerivedCommonFunction2();
   d->DerivedDerivedCommonFunction();
   ((DerivedClass2*)d)->DerivedCommonFunction2();
   ((Base*)d)->VirtualFunction();
 }

Sans hériter virtuellement de la classe de basse, vous obtiendrez un objet qui ressemble à ceci:

Au lieu de cela:

C'est-à-dire qu'il y aura 2 objets de base.

Dans la situation de l' héritage de diamant virtuel ci - dessus, après nouvelle est appelée, elle appelle le constructeur le plus dérivé et dans ce constructeur, il appelle tous les trois constructeurs dérivés passant des décalages dans sa table de table virtuelle, au lieu d'appeler simplement appeler DerivedClass1::DerivedClass1()et DerivedClass2::DerivedClass2()puis les deux appelsBase::Base()

Ce qui suit est compilé en mode de débogage -O0 donc il y aura un assemblage redondant

main:
.LFB8:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 24
        mov     edi, 48 //pass size to new
        call    operator new(unsigned long) //call new
        mov     rbx, rax  //move the address of the allocation to rbx
        mov     rdi, rbx  //move it to rdi i.e. pass to the call
        call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor] //construct on this address
        mov     QWORD PTR [rbp-24], rbx  //store the address of the object on the stack as d
DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]:
.LFB20:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB5:
        mov     rax, QWORD PTR [rbp-8] // object address now in rax 
        add     rax, 32 //increment address by 32
        mov     rdi, rax // move object address+32 to rdi i.e. pass to call
        call    Base::Base() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+8 //move address of VTT+8 to edx
        mov     rsi, rdx //pass VTT+8 address as 2nd parameter 
        mov     rdi, rax //object address as first
        call    DerivedClass1::DerivedClass1() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        add     rax, 16  //increment object address by 16
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+24  //store address of VTT+24 in edx
        mov     rsi, rdx //pass address of VTT+24 as second parameter
        mov     rdi, rax //address of object as first
        call    DerivedClass2::DerivedClass2() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+24 //move this to edx
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        mov     QWORD PTR [rax], rdx. //store address of vtable for DerivedDerivedClass+24 at the start of the object
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        add     rax, 32  // increment object address by 32
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+120 //move this to edx
        mov     QWORD PTR [rax], rdx  //store vtable for DerivedDerivedClass+120 at object+32 (Base) 
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+72 //store this in edx
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     QWORD PTR [rax+16], rdx //store vtable for DerivedDerivedClass+72 at object+16 (DerivedClass2)
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax+28], 5
.LBE5:
        nop
        leave
        ret

Il appelle Base::Base()avec un pointeur sur le décalage d'objet 32. Base stocke un pointeur sur sa table virtuelle à l'adresse qu'il reçoit et ses membres après celle-ci.

Base::Base() [base object constructor]:
.LFB11:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //stores address of object on stack (-O0)
.LBB2:
        mov     edx, OFFSET FLAT:vtable for Base+16  //puts vtable for Base+16 in edx
        mov     rax, QWORD PTR [rbp-8] //copies address of object from stack to rax
        mov     QWORD PTR [rax], rdx  //stores it address of object
        mov     rax, QWORD PTR [rbp-8] //copies address of object on stack to rax again
        mov     DWORD PTR [rax+8], 1 //stores a = 1 in the object
        mov     rax, QWORD PTR [rbp-8] //junk from -O0
        mov     DWORD PTR [rax+12], 2  //stores b = 2 in the object
.LBE2:
        nop
        pop     rbp
        ret

DerivedDerivedClass::DerivedDerivedClass()appelle ensuite DerivedClass1::DerivedClass1()avec un pointeur sur le décalage d'objet 0 et passe également l'adresse deVTT for DerivedDerivedClass+8

DerivedClass1::DerivedClass1() [base object constructor]:
.LFB14:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //address of object
        mov     QWORD PTR [rbp-16], rsi  //address of VTT+8
.LBB3:
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rdx, QWORD PTR [rax]     //address of DerivedClass1-in-DerivedDerivedClass+24 now in rdx
        mov     rax, QWORD PTR [rbp-8]   //address of object now in rax
        mov     QWORD PTR [rax], rdx     //store address of DerivedClass1-in-.. in the object
        mov     rax, QWORD PTR [rbp-8]  // address of object now in rax
        mov     rax, QWORD PTR [rax]    //address of DerivedClass1-in.. now implicitly in rax
        sub     rax, 24                 //address of DerivedClass1-in-DerivedDerivedClass+0 now in rax
        mov     rax, QWORD PTR [rax]    //value of 32 now in rax
        mov     rdx, rax                // now in rdx
        mov     rax, QWORD PTR [rbp-8]  //address of object now in rax
        add     rdx, rax                //address of object+32 now in rdx
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rax, QWORD PTR [rax+8]   //address of DerivedClass1-in-DerivedDerivedClass+72 (Base::CommonFunction()) now in rax
        mov     QWORD PTR [rdx], rax     //store at address object+32 (offset to Base)
        mov     rax, QWORD PTR [rbp-8]  //store address of object in rax, return
        mov     DWORD PTR [rax+8], 3    //store its attribute c = 3 in the object
.LBE3:
        nop
        pop     rbp
        ret
VTT for DerivedDerivedClass:
        .quad   vtable for DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+72
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+72
        .quad   vtable for DerivedDerivedClass+120
        .quad   vtable for DerivedDerivedClass+72

construction vtable for DerivedClass1-in-DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedClass1
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedClass1::VirtualFunction()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedClass1
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass1::VirtualFunction()
construction vtable for DerivedClass2-in-DerivedDerivedClass:
        .quad   16
        .quad   0
        .quad   typeinfo for DerivedClass2
        .quad   DerivedClass2::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -16
        .quad   0
        .quad   -16
        .quad   typeinfo for DerivedClass2
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass2::VirtualFunction()
vtable for DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedDerivedClass
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedDerivedClass::VirtualFunction()
        .quad   DerivedDerivedClass::DerivedDerivedCommonFunction()
        .quad   16
        .quad   -16
        .quad   typeinfo for DerivedDerivedClass
        .quad   non-virtual thunk to DerivedDerivedClass::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedDerivedClass
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedDerivedClass::VirtualFunction()

virtual thunk to DerivedClass1::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK0
virtual thunk to DerivedClass2::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK1
virtual thunk to DerivedDerivedClass::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK2
non-virtual thunk to DerivedDerivedClass::VirtualFunction():
        sub     rdi, 16
        jmp     .LTHUNK3

        .set    .LTHUNK0,DerivedClass1::VirtualFunction()
        .set    .LTHUNK1,DerivedClass2::VirtualFunction()
        .set    .LTHUNK2,DerivedDerivedClass::VirtualFunction()
        .set    .LTHUNK3,DerivedDerivedClass::VirtualFunction()

DerivedDerivedClass::DerivedDerivedClass()passe ensuite l'adresse de l'objet + 16 et l'adresse de VTT pour DerivedDerivedClass+24à DerivedClass2::DerivedClass2()qui l'assemblage est identique à l' DerivedClass1::DerivedClass1()exception de la ligne mov DWORD PTR [rax+8], 3qui a évidemment un 4 au lieu de 3 pour d = 4.

Après cela, il remplace les 3 pointeurs de table virtuelle de l'objet par des pointeurs vers les décalages dans DerivedDerivedClassla table de la représentation de cette classe.

d->VirtualFunction();:

        mov     rax, QWORD PTR [rbp-24] //store pointer to virtual table in rax 
        mov     rax, QWORD PTR [rax] //dereference and store in rax
        add     rax, 8 // call the 2nd function in the table
        mov     rdx, QWORD PTR [rax] //dereference 
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction2();:

        mov     rax, QWORD PTR [rbp-24]
        lea     rdx, [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+16]
        add     rax, 8
        mov     rax, QWORD PTR [rax]
        mov     rdi, rdx
        call    rax

d->DerivedDerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        add     rax, 16
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

((DerivedClass2*)d)->DerivedCommonFunction2();:

        cmp     QWORD PTR [rbp-24], 0
        je      .L14
        mov     rax, QWORD PTR [rbp-24]
        add     rax, 16
        jmp     .L15
.L14:
        mov     eax, 0
.L15:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L18
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, 16
        jmp     .L19
.L18:
        mov     edx, 0
.L19:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

((Base*)d)->VirtualFunction();:

        cmp     QWORD PTR [rbp-24], 0
        je      .L20
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        sub     rax, 24
        mov     rax, QWORD PTR [rax]
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        jmp     .L21
.L20:
        mov     eax, 0
.L21:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L24
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        sub     rdx, 24
        mov     rdx, QWORD PTR [rdx]
        mov     rcx, rdx
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, rcx
        jmp     .L25
.L24:
        mov     edx, 0
.L25:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx
Lewis Kelsey
la source