Was ist in C ++ eine virtuelle Basisklasse?

403

Ich möchte wissen, was eine " virtuelle Basisklasse " ist und was sie bedeutet.

Lassen Sie mich ein Beispiel zeigen:

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

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};
Popopome
quelle
sollten wir virtuelle Basisklassen in 'Mehrfachvererbung' verwenden, denn wenn Klasse A die Mitgliedsvariable int a hat und Klasse B auch das Mitglied int a hat und Klasse c die Klassen A und B erbt, wie entscheiden wir, welches 'a' verwendet werden soll?
Namit Sinha
2
@NamitSinha nein, virtuelle Vererbung ist nicht löst dieses Problem. Das Mitglied a wäre sowieso mehrdeutig
Ichthyo
@NamitSinha Virtuelle Vererbung ist kein magisches Werkzeug, um mehrere vererbungsbedingte Mehrdeutigkeiten zu beseitigen. Es "löst" ein "Problem", eine indirekte Basis mehr als einmal zu haben. Welches ist nur ein Problem, wenn es geteilt werden sollte (oft, aber nicht immer der Fall).
Neugieriger

Antworten:

533

Virtuelle Basisklassen, die bei der virtuellen Vererbung verwendet werden, verhindern, dass bei Verwendung der Mehrfachvererbung mehrere "Instanzen" einer bestimmten Klasse in einer Vererbungshierarchie angezeigt werden.

Stellen Sie sich das folgende Szenario vor:

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

Die obige Klassenhierarchie führt zu dem "gefürchteten Diamanten", der so aussieht:

  A
 / \
B   C
 \ /
  D

Eine Instanz von D besteht aus B, das A enthält, und C, das auch A enthält. Sie haben also zwei "Instanzen" (aus Mangel an einem besseren Ausdruck) von A.

Wenn Sie dieses Szenario haben, besteht die Möglichkeit von Mehrdeutigkeiten. Was passiert, wenn Sie dies tun:

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

Die virtuelle Vererbung soll dieses Problem lösen. Wenn Sie beim Erben Ihrer Klassen virtual angeben, teilen Sie dem Compiler mit, dass Sie nur eine einzige Instanz möchten.

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

Dies bedeutet, dass nur eine "Instanz" von A in der Hierarchie enthalten ist. Daher

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

Dies ist eine kleine Zusammenfassung. Lesen Sie dies und das , um weitere Informationen zu erhalten . Ein gutes Beispiel finden Sie auch hier .

ABl.
quelle
7
@Bohdan nein das tut es nicht :)
ABl.
6
@OJ. warum nicht? Sie sind komisch :)
Bohdan
15
@Bohdan verwendet das virtuelle Schlüsselwort genauso oft wie weniger, da bei Verwendung des virtuellen Schlüsselworts ein Mechanismus mit hohem Gewicht angewendet wird. So wird Ihre Programmeffizienz reduziert.
Sagar
73
Ihr "gefürchteter Diamant" -Diagramm ist verwirrend, obwohl es häufig verwendet wird. Dies ist eigentlich ein Diagramm, das Klassenvererbungsbeziehungen zeigt - kein Objektlayout. Der verwirrende Teil ist, dass, wenn wir verwenden virtual, das Objektlayout wie der Diamant aussieht; und wenn wir nicht verwenden, virtualdann sieht das Objektlayout wie eine Baumstruktur aus, die zwei As enthält
MM
5
Ich muss diese Antwort aus dem von MM dargelegten Grund ablehnen - das Diagramm drückt das Gegenteil des Beitrags aus.
David Stone
251

Über das Speicherlayout

Nebenbei bemerkt, das Problem mit dem Dreaded Diamond ist, dass die Basisklasse mehrmals vorhanden ist. Bei regelmäßiger Vererbung glauben Sie also, dass Sie:

  A
 / \
B   C
 \ /
  D

Aber im Speicherlayout haben Sie:

A   A
|   |
B   C
 \ /
  D

Dies erklärt, warum Sie beim Anruf D::foo()ein Mehrdeutigkeitsproblem haben. Das eigentliche Problem tritt jedoch auf, wenn Sie eine Mitgliedsvariable von verwenden möchten A. Nehmen wir zum Beispiel an, wir haben:

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

Wenn Sie für den Zugriff versuchen werden m_iValueaus D, wird der Compiler protestieren, weil in der Hierarchie, wird es zwei sehen m_iValue, nicht ein. Und wenn Sie beispielsweise eines ändern B::m_iValue(das ist das A::m_iValueübergeordnete Element von B), C::m_iValuewird es nicht geändert (das ist das A::m_iValueübergeordnete Element von C).

Hier bietet sich die virtuelle Vererbung an, da Sie mit nicht nur einer foo()Methode, sondern auch nur einer einzigen Methode zu einem echten Diamant-Layout zurückkehren können m_iValue.

Was könnte schiefgehen?

Vorstellen:

  • A hat einige grundlegende Funktionen.
  • B fügt eine Art cooles Array von Daten hinzu (zum Beispiel)
  • CFügt eine coole Funktion wie ein Beobachtermuster hinzu (z. B. Ein m_iValue).
  • Derbt von Bund Cund damit von A.

Bei normaler Vererbung ist das Ändern m_iValuevon nicht Deindeutig und muss behoben werden. Selbst wenn dies der Fall ist, befinden sich zwei im m_iValuesInneren D. Denken Sie also besser daran und aktualisieren Sie die beiden gleichzeitig.

Bei der virtuellen Vererbung ist das Ändern m_iValuevon Din Ordnung ... Aber ... Nehmen wir an, Sie haben D. Über die CBenutzeroberfläche haben Sie einen Beobachter angehängt. Und über seine BBenutzeroberfläche aktualisieren Sie das coole Array, was den Nebeneffekt hat, dass es sich direkt ändert m_iValue...

Da die Änderung m_iValuedirekt (ohne Verwendung einer virtuellen Zugriffsmethode) erfolgt, wird der Beobachter, der "durchhört" C, nicht aufgerufen, da der Code, der das Abhören implementiert, vorhanden ist Cund Bnichts davon weiß ...

Fazit

Wenn Sie einen Diamanten in Ihrer Hierarchie haben, bedeutet dies, dass Sie mit einer Wahrscheinlichkeit von 95% etwas mit dieser Hierarchie falsch gemacht haben.

paercebal
quelle
Ihr "Was könnte schief gehen" ist auf den direkten Zugriff auf ein Basismitglied zurückzuführen, nicht auf die Mehrfachvererbung. Wenn Sie 'B' loswerden, haben Sie das gleiche Problem. Die Grundregel: 'Wenn es nicht privat ist, sollte es virtuell sein' vermeidet das Problem. M_iValue ist nicht virtuell und sollte daher privat sein
Chris Dodd
4
@ Chris Dodd: Nicht genau. Was mit m_iValue passiert, wäre mit jedem Symbol passiert ( z. B. typedef, Mitgliedsvariable, Mitgliedsfunktion, Umwandlung in die Basisklasse usw. ). Dies ist wirklich ein Problem der Mehrfachvererbung, ein Problem, das Benutzer beachten sollten, um die Mehrfachvererbung korrekt zu verwenden, anstatt den Java-Weg zu gehen und zu folgern: "Mehrfachvererbung ist 100% böse, machen wir das mit Schnittstellen".
Paercebal
Hallo, wenn wir ein virtuelles Schlüsselwort verwenden, gibt es nur eine Kopie von A. Meine Frage ist, woher wissen wir, ob es von B oder C kommt? Ist meine Frage überhaupt gültig?
user875036
@ user875036: A kommt sowohl von B als auch von C. In der Tat ändert die Virtualität einige Dinge (z. B. ruft D den Konstruktor von A auf, nicht B oder C). Sowohl B als auch C (und D) haben einen Zeiger auf A.
Paercebal
3
FWIW, falls sich jemand wundert, können Mitgliedsvariablen nicht virtuell sein - virtuell ist ein Spezifizierer für Funktionen . SO Referenz: stackoverflow.com/questions/3698831/…
rholmes
34

Das Erklären der Mehrfachvererbung mit virtuellen Basen erfordert Kenntnisse des C ++ - Objektmodells. Eine klare Erklärung des Themas erfolgt am besten in einem Artikel und nicht in einem Kommentarfeld.

Die beste, lesbare Erklärung, die alle meine Zweifel zu diesem Thema gelöst hat, war dieser Artikel: http://www.phpcompiler.org/articles/virtualinheritance.html

Sie müssen wirklich nichts anderes zu diesem Thema lesen (es sei denn, Sie sind ein Compiler-Autor), nachdem Sie das gelesen haben ...

lenkite
quelle
10

Eine virtuelle Basisklasse ist eine Klasse, die nicht instanziiert werden kann: Sie können kein direktes Objekt daraus erstellen.

Ich denke, Sie verwechseln zwei sehr unterschiedliche Dinge. Virtuelle Vererbung ist nicht dasselbe wie eine abstrakte Klasse. Die virtuelle Vererbung ändert das Verhalten von Funktionsaufrufen. Manchmal werden Funktionsaufrufe aufgelöst, die ansonsten nicht eindeutig wären. Manchmal wird die Funktionsaufrufbehandlung auf eine andere Klasse verschoben, als dies bei einer nicht virtuellen Vererbung zu erwarten wäre.

wilhelmtell
quelle
7

Ich möchte zu OJs freundlichen Klarstellungen hinzufügen.

Virtuelle Vererbung ist nicht ohne Preis. Wie bei allen virtuellen Dingen erhalten Sie einen Leistungseinbruch. Es gibt einen Weg um diesen Performance-Hit, der möglicherweise weniger elegant ist.

Anstatt den Diamanten durch virtuelles Ableiten zu brechen, können Sie dem Diamanten eine weitere Schicht hinzufügen, um Folgendes zu erhalten:

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

Keine der Klassen erbt virtuell, alle erben öffentlich. Die Klassen D21 und D22 verbergen dann die virtuelle Funktion f (), die für DD nicht eindeutig ist, möglicherweise indem die Funktion als privat deklariert wird. Sie würden jeweils eine Wrapper-Funktion definieren, f1 () bzw. f2 (), die jeweils klassenlokales (privates) f () aufrufen und so Konflikte lösen. Klasse DD ruft f1 () auf, wenn sie D11 :: f () will, und f2 (), wenn sie D12 :: f () will. Wenn Sie die Wrapper inline definieren, erhalten Sie wahrscheinlich einen Overhead von etwa Null.

Wenn Sie D11 und D12 ändern können, können Sie natürlich den gleichen Trick in diesen Klassen ausführen, aber oft ist dies nicht der Fall.

wilhelmtell
quelle
2
Hierbei geht es nicht um mehr oder weniger elegante oder das Auflösen von Mehrdeutigkeiten (Sie können dafür immer explizite xxx :: -Spezifikationen verwenden). Bei der nicht virtuellen Vererbung verfügt jede Instanz der Klasse DD über zwei unabhängige Instanzen von B. Sobald die Klasse ein einzelnes nicht statisches Datenelement hat, unterscheiden sich die virtuelle und die nicht virtuelle Vererbung nicht nur durch die Syntax.
user3489112
@ user3489112 Sobald ... nichts. Virtuelle und nicht virtuelle Vererbung unterscheiden sich semantisch.
Neugieriger
4

Zusätzlich zu dem, was bereits über multiple und virtuelle Vererbung (en) gesagt wurde, gibt es einen sehr interessanten Artikel in Dr. Dobbs Journal: Multiple Vererbung als nützlich erachtet

Luc Hermitte
quelle
1

Du bist ein bisschen verwirrend. Ich weiß nicht, ob Sie einige Konzepte verwechseln.

Sie haben keine virtuelle Basisklasse in Ihrem OP. Sie haben nur eine Basisklasse.

Sie haben eine virtuelle Vererbung durchgeführt. Dies wird normalerweise bei der Mehrfachvererbung verwendet, sodass mehrere abgeleitete Klassen die Mitglieder der Basisklasse verwenden, ohne sie zu reproduzieren.

Eine Basisklasse mit einer rein virtuellen Funktion wird nicht instanziiert. Dies erfordert die Syntax, mit der Paul arbeitet. Es wird normalerweise verwendet, damit abgeleitete Klassen diese Funktionen definieren müssen.

Ich möchte das nicht mehr erklären, weil ich nicht ganz verstehe, was Sie verlangen.

Baltimark
quelle
1
Eine "Basisklasse", die in einer virtuellen Vererbung verwendet wird, wird zu einer "virtuellen Basisklasse" (im Kontext dieser genauen Vererbung).
Luc Hermitte
1

Dies bedeutet, dass ein Aufruf einer virtuellen Funktion an die "richtige" Klasse weitergeleitet wird.

C ++ FAQ Lite FTW.

Kurz gesagt, es wird häufig in Szenarien mit Mehrfachvererbung verwendet, in denen eine "Diamant" -Hierarchie gebildet wird. Durch die virtuelle Vererbung wird dann die in der unteren Klasse erzeugte Mehrdeutigkeit aufgehoben, wenn Sie die Funktion in dieser Klasse aufrufen und die Funktion entweder in die Klasse D1 oder D2 über dieser unteren Klasse aufgelöst werden muss. In der FAQ finden Sie ein Diagramm und Details.

Es wird auch in der Schwesterdelegation verwendet , eine leistungsstarke Funktion (wenn auch nicht für schwache Nerven). Siehe diese FAQ.

Siehe auch Punkt 40 in Effective C ++ 3. Ausgabe (43 in 2. Ausgabe).

wilhelmtell
quelle
1

Ausführbares Verwendungsbeispiel für die Diamond-Vererbung

Dieses Beispiel zeigt, wie eine virtuelle Basisklasse im typischen Szenario verwendet wird: Lösen der Diamantvererbung.

#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 郝海东 冠状 病 六四 事件 法轮功
quelle
2
assert(A::aDefault == 0);von der Hauptfunktion gibt mir einen Kompilierungsfehler: aDefault is not a member of Amit gcc 5.4.0. Was soll es tun?
SebNag
@SebTu ah danke, nur etwas, das ich vergessen habe, aus Copy Paste zu entfernen, habe es jetzt entfernt. Das Beispiel sollte auch ohne es noch aussagekräftig sein.
Ciro Santilli 20 冠状 病 六四 事件 20
0

Virtuelle Klassen sind nicht dasselbe wie virtuelle Vererbung. Virtuelle Klassen, die Sie nicht instanziieren können, virtuelle Vererbung ist etwas ganz anderes.

Wikipedia beschreibt es besser als ich kann. http://en.wikipedia.org/wiki/Virtual_inheritance

Bradtgmurray
quelle
6
In C ++ gibt es keine "virtuellen Klassen". Es gibt jedoch "virtuelle Basisklassen", die in Bezug auf eine bestimmte Vererbung "virtuell" sind. Was Sie meinen, ist das, was offiziell als "abstrakte Klassen" bezeichnet wird.
Luc Hermitte
@ LucHermitte, es gibt definitiv virtuelle Klassen in C ++. Überprüfen Sie dies: en.wikipedia.org/wiki/Virtual_class .
Rafid
"Fehler: 'virtuell' kann nur für Funktionen angegeben werden". Ich weiß nicht, welche Sprache das ist. Aber es gibt definitiv keine virtuelle Klasse in C ++.
Luc Hermitte
0

Regelmäßige Vererbung

Bei der typischen 3-Ebenen-Vererbung ohne Diamanten ohne virtuelle Vererbung wird beim Instanziieren eines neuen, am meisten abgeleiteten Objekts new aufgerufen und die für das Objekt erforderliche Größe vom Compiler aus dem Klassentyp aufgelöst und an new übergeben.

neu hat eine Unterschrift:

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

Und ruft an mallocund gibt den leeren Zeiger zurück

Dies wird dann an den Konstruktor des am meisten abgeleiteten Objekts übergeben, der sofort den mittleren Konstruktor und dann den mittleren Konstruktor sofort den Basiskonstruktor aufruft. Die Basis speichert dann einen Zeiger auf ihre virtuelle Tabelle am Anfang des Objekts und anschließend auf seine Attribute. Dies kehrt dann zum mittleren Konstruktor zurück, der seinen virtuellen Tabellenzeiger an derselben Stelle und dann seine Attribute nach den Attributen speichert, die vom Basiskonstruktor gespeichert worden wären. Es kehrt zum am meisten abgeleiteten Konstruktor zurück, der einen Zeiger auf seine virtuelle Tabelle am selben Speicherort und dann seine Attribute nach den Attributen speichert, die vom mittleren Konstruktor gespeichert worden wären.

Da der virtuelle Tabellenzeiger überschrieben wird, ist der virtuelle Tabellenzeiger immer die am meisten abgeleitete Klasse. Virtualität breitet sich in Richtung der am meisten abgeleiteten Klasse aus. Wenn eine Funktion in der Mittelklasse virtuell ist, ist sie in der am meisten abgeleiteten Klasse virtuell, jedoch nicht in der Basisklasse. Wenn Sie eine Instanz der am meisten abgeleiteten Klasse polymorph in einen Zeiger auf die Basisklasse umwandeln, löst der Compiler dies nicht in einen indirekten Aufruf der virtuellen Tabelle auf und ruft stattdessen die Funktion direkt auf A::function(). Wenn eine Funktion für den Typ, in den Sie sie umgewandelt haben, virtuell ist, wird sie in einen Aufruf der virtuellen Tabelle aufgelöst, die immer die der am meisten abgeleiteten Klasse ist. Wenn es für diesen Typ nicht virtuell ist, ruft es einfach Type::function()den Objektzeiger auf und übergibt ihn an ihn.

Wenn ich Zeiger auf seine virtuelle Tabelle sage, ist es tatsächlich immer ein Versatz von 16 in die virtuelle Tabelle.

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

virtualwird in mehr abgeleiteten Klassen nicht erneut benötigt, wenn es in einer weniger abgeleiteten Klasse virtuell ist, weil es sich ausbreitet. Es kann jedoch verwendet werden, um zu zeigen, dass die Funktion tatsächlich eine virtuelle Funktion ist, ohne die Klassen überprüfen zu müssen, die die Typdefinitionen erbt.

override ist ein weiterer Compiler-Guard, der besagt, dass diese Funktion etwas überschreibt, und wenn dies nicht der Fall ist, wird ein Compiler-Fehler ausgegeben.

= 0 bedeutet, dass dies eine abstrakte Funktion ist

final verhindert, dass eine virtuelle Funktion erneut in einer abgeleiteten Klasse implementiert wird, und stellt sicher, dass die virtuelle Tabelle der am meisten abgeleiteten Klasse die endgültige Funktion dieser Klasse enthält.

= default macht in der Dokumentation deutlich, dass der Compiler die Standardimplementierung verwendet

= delete Geben Sie einen Compilerfehler aus, wenn versucht wird, dies aufzurufen

Virtuelle Vererbung

Erwägen

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();
 }

Ohne die Bassklasse virtuell zu erben, erhalten Sie ein Objekt, das so aussieht:

An Stelle von:

Dh es wird 2 Basisobjekte geben.

In der virtuellen Situation über Vererbung Diamant, nach neuen genannt wird, ruft es die meisten abgeleitete Konstruktor und in diesem Konstruktor, ruft es alle 3 abgeleitet Konstrukteurs - Offsets in die virtuelle Tabelle Tabelle vorbei, statt nur anrufen , anrufen DerivedClass1::DerivedClass1()und DerivedClass2::DerivedClass2()und jene dann beide BerufungBase::Base()

Das Folgende wird alle im Debug-Modus -O0 kompiliert, sodass eine redundante Assembly erfolgt

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

Es ruft Base::Base()mit einem Zeiger auf den Objektversatz 32 auf. Base speichert einen Zeiger auf seine virtuelle Tabelle an der Adresse, die es empfängt, und auf seine Mitglieder danach.

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()ruft dann DerivedClass1::DerivedClass1()mit einem Zeiger auf den Objektoffset 0 auf und übergibt auch die Adresse vonVTT 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()übergibt dann die Adresse des Objekts + 16 und die Adresse für VTT DerivedDerivedClass+24zu DerivedClass2::DerivedClass2()den Montag identisch ist zu DerivedClass1::DerivedClass1()der Linie ausnehmen , mov DWORD PTR [rax+8], 3die offensichtlich ein 4 anstelle von 3 für besitzt d = 4.

Danach werden alle 3 virtuellen Tabellenzeiger im Objekt durch Zeiger auf Offsets in DerivedDerivedClassder vtable der Darstellung für diese Klasse ersetzt.

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
quelle