Czym jest wirtualna klasa bazowa w C ++?

403

Chcę wiedzieć, czym jest „ wirtualna klasa bazowa ” i co ona oznacza.

Pokażę przykład:

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

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};
popopom
źródło
czy powinniśmy używać wirtualnych klas bazowych w „wielokrotnym dziedziczeniu”, ponieważ jeśli klasa A ma zmienną członkowską int a, a klasa B ma również element int a, a klasa c dziedziczy klasy A i B, w jaki sposób zdecydować, którego „a” użyć?
Namit Sinha
2
@NamitSinha nie, wirtualne dziedziczenie nie rozwiązuje tego problemu. Członek a i tak byłby niejednoznaczny
Ichthyo
@NamitSinha Wirtualne dziedziczenie nie jest magicznym narzędziem do usuwania wielu niejasności związanych z dziedziczeniem. „Rozwiązuje” „problem” posiadania pośredniej bazy więcej niż jeden raz. Jest to problem tylko wtedy, gdy miał być udostępniany (często, ale nie zawsze).
ciekawy,

Odpowiedzi:

533

Wirtualne klasy podstawowe, używane w wirtualnym dziedziczeniu, są sposobem na zapobieganie pojawianiu się wielu „wystąpień” danej klasy w hierarchii dziedziczenia podczas korzystania z wielokrotnego dziedziczenia.

Rozważ następujący scenariusz:

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

Powyższa hierarchia klas skutkuje „przerażającym diamentem”, który wygląda następująco:

  A
 / \
B   C
 \ /
  D

Instancja D będzie złożona z B, która obejmuje A, i C, która również obejmuje A. Zatem masz dwa „wystąpienia” (z braku lepszego wyrażenia) A.

Kiedy masz taki scenariusz, masz możliwość niejasności. Co się stanie, gdy to zrobisz:

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

Wirtualne dziedziczenie służy rozwiązaniu tego problemu. Kiedy określasz wirtualny podczas dziedziczenia klas, mówisz kompilatorowi, że chcesz tylko jednej instancji.

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

Oznacza to, że istnieje tylko jedna „instancja” A zawarta w hierarchii. W związku z tym

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

To jest mini podsumowanie. Aby uzyskać więcej informacji, przeczytaj to i to . Dobry przykład jest również dostępny tutaj .

Dz.U.
źródło
7
@Bohdan nie to nie :)
Dz.U.
6
@OJ. Dlaczego nie? Są zabawne :)
Bohdan
15
@Bohdan używa wirtualnego słowa kluczowego w mniejszym stopniu, ponieważ kiedy używamy wirtualnego słowa kluczowego, stosowany jest mechanizm dużej wagi. Tak więc wydajność twojego programu zostanie zmniejszona.
Sagar
73
Twój diagram „przerażającego diamentu” jest mylący, choć wydaje się, że jest powszechnie używany. W rzeczywistości jest to diagram przedstawiający relacje dziedziczenia klas - a nie układ obiektu. Mylące jest to, że jeśli używamy virtual, układ obiektu wygląda jak diament; a jeśli nie używamy, virtualukład obiektu wygląda jak struktura drzewa, która zawiera dwa As
MM
5
Muszę głosować za odpowiedzią z powodu przedstawionego przez MM - schemat wyraża przeciwieństwo postu.
David Stone
251

O układzie pamięci

Na marginesie, problem z Przerażonym Diamentem polega na tym, że klasa podstawowa występuje wielokrotnie. Więc przy regularnym dziedziczeniu uważasz, że masz:

  A
 / \
B   C
 \ /
  D

Ale w układzie pamięci masz:

A   A
|   |
B   C
 \ /
  D

To wyjaśnia, dlaczego podczas połączenia D::foo()masz problem z dwuznacznością. Ale prawdziwy problem pojawia się, gdy chcesz użyć zmiennej członka A. Załóżmy na przykład, że mamy:

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

Gdy spróbujesz uzyskać dostęp m_iValuez D, kompilator zaprotestuje, ponieważ w hierarchii zobaczy dwa m_iValue, a nie jeden. A jeśli zmodyfikujesz jeden, powiedzmy B::m_iValue(to jest A::m_iValuenadrzędny B), C::m_iValuenie zostanie zmodyfikowany (to jest A::m_iValuenadrzędny C).

Tutaj przydaje się wirtualne dziedziczenie, ponieważ dzięki niemu wrócisz do prawdziwego układu diamentów, używając nie tylko jednej foo()metody, ale także jednej i tylko jednej m_iValue.

Co mogłoby pójść źle?

Wyobrażać sobie:

  • A ma podstawową funkcję.
  • B dodaje do tego jakiś fajny zestaw danych (na przykład)
  • Cdodaje do tego jakąś fajną funkcję, taką jak wzorzec obserwatora (na przykład włączony m_iValue).
  • Ddziedziczy po Bi C, a zatem i od A.

W przypadku normalnego dziedziczenia zmiana m_iValuez Djest niejednoznaczna i należy to rozwiązać. Nawet jeśli tak, są dwa w m_iValuesśrodku D, więc lepiej o tym pamiętaj i zaktualizuj oba jednocześnie.

W przypadku wirtualnego dziedziczenia zmiana m_iValuez Djest w porządku ... Ale ... Powiedzmy, że masz D. Poprzez jego Cinterfejs dołączyłeś obserwatora. Za pomocą tego Binterfejsu aktualizujesz fajną tablicę, której efektem ubocznym jest bezpośrednia zmiana m_iValue...

Ponieważ zmiana m_iValueodbywa się bezpośrednio (bez użycia metody wirtualnego akcesorium), obserwator „nasłuchujący” Cnie zostanie wywołany, ponieważ kod implementujący nasłuchiwanie jest włączony Ci Bnie wie o tym ...

Wniosek

Jeśli masz diament w swojej hierarchii, oznacza to, że masz 95% prawdopodobieństwa, że ​​zrobiłeś coś złego z tą hierarchią.

paercebal
źródło
Twoje „to, co może pójść nie tak”, wynika z bezpośredniego dostępu do członka podstawowego, a nie z powodu wielokrotnego dziedziczenia. Pozbądź się „B” i masz ten sam problem. Podstawowa zasada: „jeśli nie jest prywatny, powinien być wirtualny” pozwala uniknąć problemu. M_iValue nie jest wirtualny i dlatego powinien być prywatny
Chris Dodd
4
@Chris Dodd: Niezupełnie. To, co dzieje się z m_iValue, miałoby miejsce w przypadku dowolnego symbolu ( np. Typedef, zmienna składowa, funkcja składowa , rzut na klasę podstawową itp .). To naprawdę jest problem wielokrotnego dziedziczenia, problem, który użytkownicy powinni pamiętać o prawidłowym korzystaniu z wielokrotnego dziedziczenia, zamiast iść w Javie i dojść do wniosku: „Wielokrotne dziedziczenie jest w 100% złe, zróbmy to z interfejsami”.
paercebal 30.10.10
Cześć, Kiedy użyjemy wirtualnego słowa kluczowego, będzie tylko jedna kopia A. Moje pytanie brzmi, skąd wiemy, czy pochodzi od B czy C? Czy moje pytanie jest w ogóle ważne?
user875036
@ user875036: A pochodzi zarówno z B, jak i C. Rzeczywiście, wirtualność zmienia kilka rzeczy (np. D wywoła konstruktor A, a nie B, ani C). Zarówno B, jak i C (i D) mają wskaźnik do A.
paercebal
3
FWIW, jeśli ktoś się zastanawia, zmienne składowe nie mogą być wirtualne - wirtualny jest specyfikatorem funkcji . Referencje SO: stackoverflow.com/questions/3698831/...
rholmes 23.09.16
34

Wyjaśnienie wielokrotnego dziedziczenia za pomocą wirtualnych baz wymaga znajomości modelu obiektowego C ++. Wyjaśnienie tego tematu najlepiej jest zrobić w artykule, a nie w polu komentarza.

Najlepszym, czytelnym wyjaśnieniem, które rozwiązało wszystkie moje wątpliwości w tym temacie, był ten artykuł: http://www.phpcompiler.org/articles/virtualinheritance.html

Naprawdę nie będziesz musiał czytać niczego więcej na ten temat (chyba że jesteś pisarzem kompilatora) po przeczytaniu tego ...

lenkite
źródło
10

Wirtualna klasa podstawowa to klasa, której nie można utworzyć: nie można na jej podstawie tworzyć bezpośrednich obiektów.

Myślę, że mylisz dwie bardzo różne rzeczy. Dziedziczenie wirtualne nie jest tym samym, co klasa abstrakcyjna. Dziedziczenie wirtualne modyfikuje zachowanie wywołań funkcji; czasami rozwiązuje wywołania funkcji, które w innym przypadku byłyby niejednoznaczne, czasami odkłada obsługę wywołań funkcji na klasę inną niż ta, której można oczekiwać w dziedziczeniu innym niż wirtualny.

wilhelmtell
źródło
7

Chciałbym dodać do uprzejmych wyjaśnień Dz.U.

Dziedziczenie wirtualne nie przychodzi bez ceny. Podobnie jak w przypadku wszystkich rzeczy wirtualnych, masz hit wydajności. Istnieje sposób obejścia tego hitu wydajności, który może być mniej elegancki.

Zamiast łamać diament, czerpiąc wirtualnie, możesz dodać kolejną warstwę do diamentu, aby uzyskać coś takiego:

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

Żadna z klas nie dziedziczy wirtualnie, wszystkie dziedziczą publicznie. Klasy D21 i D22 będą wtedy ukrywać funkcję wirtualną f (), która jest niejednoznaczna dla DD, być może przez uznanie tej funkcji za prywatną. Każdy z nich zdefiniowałby odpowiednio funkcję opakowania, odpowiednio f1 () i f2 (), każde wywołujące klasę lokalną (prywatną) f (), rozwiązując w ten sposób konflikty. Klasa DD wywołuje f1 (), jeśli chce D11 :: f () i f2 (), jeśli chce D12 :: f (). Jeśli zdefiniujesz zawijanie w linii, prawdopodobnie uzyskasz około zera narzutu.

Oczywiście, jeśli możesz zmienić D11 i D12, możesz wykonać tę samą sztuczkę w tych klasach, ale często tak nie jest.

wilhelmtell
źródło
2
Nie jest to kwestia mniej lub bardziej elegancka lub rozwiązywanie dwuznaczności (zawsze można do tego użyć wyraźnych specyfikacji xxx ::). W przypadku dziedziczenia innego niż wirtualne, każda instancja klasy DD ma dwie niezależne instancje B. Gdy tylko klasa ma pojedynczy niestatyczny element danych, dziedziczenie wirtualne i nie-wirtualne różnią się nie tylko składnią.
user3489112,
@ user3489112 Jak tylko ... nic. Dziedziczenie wirtualne i nie wirtualne różnią się semantycznie, kropka.
ciekawy,
1

Jesteś trochę zagmatwany. Nie wiem, czy mieszacie jakieś koncepcje.

Nie masz wirtualnej klasy bazowej w swoim OP. Masz tylko klasę podstawową.

Zrobiłeś wirtualne dziedzictwo. Jest to zwykle używane w przypadku wielokrotnego dziedziczenia, dzięki czemu wiele klas pochodnych korzysta z elementów klasy podstawowej bez ich odtwarzania.

Klasa podstawowa z czystą funkcją wirtualną nie jest tworzona. wymaga to składni, którą zna Paul. Jest zwykle używany, aby klasy pochodne musiały zdefiniować te funkcje.

Nie chcę więcej o tym wyjaśniać, ponieważ nie rozumiem, o co pytasz.

Baltimark
źródło
1
„Klasa podstawowa” używana w wirtualnym dziedziczeniu staje się „wirtualną klasą podstawową” (w kontekście tego dokładnego dziedziczenia).
Luc Hermitte,
1

Oznacza to, że wywołanie funkcji wirtualnej zostanie przekierowane do „właściwej” klasy.

C ++ FAQ Lite FTW.

Krótko mówiąc, często stosuje się go w scenariuszach wielokrotnego dziedziczenia, w których powstaje hierarchia „diamentowa”. Dziedziczenie wirtualne przełamie dwuznaczność powstałą w dolnej klasie, gdy wywołasz funkcję w tej klasie, a funkcja musi zostać rozstrzygnięta na klasę D1 lub D2 powyżej tej dolnej klasy. Diagram i szczegóły znajdują się w części FAQ .

Jest również stosowany w delegacji siostrzanej , co jest potężną funkcją (choć nie dla osób o słabym sercu). Zobacz to FAQ.

Zobacz także punkt 40 w Efektywnym trzecim wydaniu C ++ (43 w drugim wydaniu).

wilhelmtell
źródło
1

Przykład użycia dziedziczonego diamentu

Ten przykład pokazuje, jak używać wirtualnej klasy bazowej w typowym scenariuszu: w celu rozwiązania dziedziczenia diamentów.

#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
źródło
2
assert(A::aDefault == 0);z głównej funkcji daje mi błąd kompilacji: aDefault is not a member of Aużycie gcc 5.4.0. Co to ma robić?
SebNag
@SebTu ah dzięki, tylko coś, co zapomniałem usunąć z kopii wklej, usunąłem to teraz. Bez tego przykład nadal powinien mieć sens.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Klasy wirtualne to nie to samo, co wirtualne dziedziczenie. Wirtualne klasy, których nie można utworzyć, wirtualne dziedziczenie to coś zupełnie innego.

Wikipedia opisuje to lepiej niż potrafię. http://en.wikipedia.org/wiki/Virtual_inheritance

bradtgmurray
źródło
6
W C ++ nie ma czegoś takiego jak „klasy wirtualne”. Istnieją jednak „wirtualne klasy podstawowe”, które są „wirtualne” w odniesieniu do danego spadku. To, co polecasz, to tak zwane „klasy abstrakcyjne”.
Luc Hermitte
@LucHermitte, w C ++ są zdecydowanie wirtualne klasy. Sprawdź to: en.wikipedia.org/wiki/Virtual_class .
Rafid
„błąd:„ wirtualny ”można określić tylko dla funkcji”. Nie wiem w jakim języku to jest. Ale zdecydowanie nie ma czegoś takiego jak klasa wirtualna w C ++.
Luc Hermitte
0

Regularne dziedziczenie

W przypadku typowego 3-poziomowego nie-diamentowego dziedziczenia niebędącego wirtualnym dziedziczeniem podczas tworzenia nowego najbardziej pochodnego obiektu wywoływane jest nowe, a rozmiar wymagany dla obiektu jest określany przez typ kompilatora i przekazywany do nowego.

nowy ma podpis:

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

I wykonuje połączenie malloc, zwracając wskaźnik pustki

Jest on następnie przekazywany do konstruktora najbardziej pochodnego obiektu, który natychmiast wywoła środkowy konstruktor, a następnie środkowy konstruktor natychmiast wywoła konstruktor podstawowy. Baza następnie przechowuje wskaźnik do wirtualnej tabeli na początku obiektu, a następnie za nim atrybutów. Następnie wraca do środkowego konstruktora, który zapisze swój wirtualny wskaźnik tabeli w tej samej lokalizacji, a następnie jego atrybuty po atrybutach, które byłyby przechowywane przez konstruktora podstawowego. Powraca do najbardziej pochodnego konstruktora, który przechowuje wskaźnik do swojej wirtualnej tabeli w tym samym miejscu, a następnie jego atrybuty po atrybutach, które byłyby przechowywane przez środkowy konstruktor.

Ponieważ wskaźnik wirtualnej tabeli jest nadpisywany, wskaźnik wirtualnej tabeli zawsze jest jedną z najbardziej pochodnych klas. Wirtualność rozprzestrzenia się w kierunku klasy najbardziej pochodnej, więc jeśli funkcja jest wirtualna w klasie średniej, będzie wirtualna w klasie najbardziej pochodnej, ale nie w klasie bazowej. Jeśli odrzucisz polimorficznie instancję klasy najbardziej pochodnej do wskaźnika do klasy podstawowej, kompilator nie rozwiąże tego w wywołaniu pośrednim do tabeli wirtualnej, a zamiast tego wywoła funkcję bezpośrednio A::function(). Jeśli funkcja jest wirtualna dla typu, do którego ją rzuciłeś, to rozwiąże wywołanie do wirtualnej tabeli, która zawsze będzie klasą najbardziej pochodną. Jeśli nie jest wirtualny dla tego typu, po prostu wywoła Type::function()i przekaże do niego wskaźnik obiektu, rzutując na Typ.

W rzeczywistości, gdy mówię wskaźnik do wirtualnej tabeli, w rzeczywistości zawsze jest to przesunięcie o 16 względem wirtualnej tabeli.

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

virtualnie jest ponownie wymagany w klasach pochodnych, jeśli jest wirtualny w klasie mniej pochodnej, ponieważ się propaguje. Ale można go użyć do pokazania, że ​​funkcja jest rzeczywiście funkcją wirtualną, bez konieczności sprawdzania klas, które dziedziczy definicje typów.

override jest kolejną ochroną kompilatora, która mówi, że ta funkcja coś przesłania, a jeśli nie, to zgłasza błąd kompilatora.

= 0 oznacza, że ​​jest to funkcja abstrakcyjna

final zapobiega ponownemu wdrożeniu funkcji wirtualnej w klasie bardziej pochodnej i upewni się, że wirtualna tabela najbardziej pochodnej klasy zawiera ostateczną funkcję tej klasy.

= default wyjaśnia w dokumentacji, że kompilator użyje domyślnej implementacji

= delete podać błąd kompilatora, jeśli nastąpi próba wywołania tego

Dziedziczenie wirtualne

Rozważać

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

Bez dziedziczenia klasy basowej otrzymasz obiekt, który wygląda następująco:

Zamiast tego:

Tj. Będą 2 obiekty podstawowe.

W wirtualnym diamentowej spadków powyżej sytuacji, gdy nowy nazywa, wywołuje konstruktor większości pochodzący iw tym konstruktora, wywołuje wszystkie 3 konstruktorów pochodzące przechodzących przesunięć w swoim wirtualnym stół, zamiast dzwonić po prostu dzwoni DerivedClass1::DerivedClass1()i DerivedClass2::DerivedClass2()czym te oba wywołującyBase::Base()

Wszystko zostało skompilowane w trybie debugowania -O0, więc będzie zbędny zestaw

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

Wywołuje Base::Base()wskaźnik za pomocą przesunięcia obiektu 32. Baza przechowuje wskaźnik do wirtualnej tabeli pod adresem, który otrzymuje, a członkowie po nim.

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()następnie wywołuje DerivedClass1::DerivedClass1()za pomocą wskaźnika do obiektu offset 0, a także przekazuje adresVTT 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()następnie przekazuje adres obiektu + 16 i adres dla VTT DerivedDerivedClass+24do DerivedClass2::DerivedClass2()których montaż jest identyczna DerivedClass1::DerivedClass1()z wyjątkiem linii mov DWORD PTR [rax+8], 3, która ma oczywiście 4 zamiast 3 na d = 4.

Następnie zastępuje wszystkie 3 wirtualne wskaźniki tabeli w obiekcie wskaźnikami przesunięć w tabeli DerivedDerivedClassvtable do reprezentacji dla tej klasy.

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
źródło