In C ++, che cos'è una classe di base virtuale?


403

Voglio sapere cos'è una " classe di base virtuale " e cosa significa.

Lasciami mostrare un esempio:

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

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

dovremmo usare le classi di base virtuali in "ereditarietà multipla" perché se la classe A ha la variabile membro int a e la classe B ha anche il membro int a e la classe c eredita le classi A e B come possiamo decidere quale "a" usare?
Namit Sinha,

2
@NamitSinha no, l'eredità virtuale non risolve questo problema. Il membro a sarebbe comunque ambiguo
Ichthyo l'

@NamitSinha L'eredità virtuale non è uno strumento magico per rimuovere più ambiguità legate all'ereditarietà. "Risolve" un "problema" di avere una base indiretta più di una volta. Il che è solo un problema se si intendeva condividere (spesso ma non sempre il caso).
curiousguy il

Risposte:


533

Le classi di base virtuali, utilizzate nell'ereditarietà virtuale, sono un modo per impedire che più "istanze" di una data classe compaiano in una gerarchia di ereditarietà quando si usa l'ereditarietà multipla.

Considera il seguente scenario:

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

La gerarchia di classe sopra risulta nel "diamante temuto" che assomiglia a questo:

  A
 / \
B   C
 \ /
  D

Un'istanza di D sarà composta da B, che include A, e C che include anche A. Quindi hai due "istanze" (per mancanza di una migliore espressione) di A.

Quando hai questo scenario, hai la possibilità di ambiguità. Cosa succede quando lo fai:

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

L'eredità virtuale è lì per risolvere questo problema. Quando specifichi virtuale quando erediti le tue classi, stai dicendo al compilatore che vuoi solo una singola istanza.

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

Ciò significa che esiste una sola "istanza" di A inclusa nella gerarchia. Quindi

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

Questo è un mini riassunto. Per ulteriori informazioni, leggi questo e questo . Un buon esempio è disponibile anche qui .


7
@Bohdan no, no :)
GU.

6
@OJ. perchè no? Sono esilaranti :)
Bohdan il

15
@Bohdan usa parole chiave virtuali tanto quanto meno, perché quando usiamo parole chiave virtuali, viene applicato un meccanismo pesante. Pertanto, l'efficienza del programma verrà ridotta.
Sagar,

73
Il diagramma "diamante temuto" è confuso, anche se sembra essere comunemente usato. Questo è in realtà un diagramma che mostra le relazioni di ereditarietà delle classi, non un layout di oggetto. La parte confusa è che se lo usiamo virtual, il layout dell'oggetto sembra il diamante; e se non lo utilizziamo, virtualil layout dell'oggetto sembra una struttura ad albero che contiene due Asecondi
MM

5
Devo sottovalutare questa risposta per il motivo indicato da MM - il diagramma esprime l'opposto del post.
David Stone,

251

Informazioni sul layout della memoria

Come nota a margine, il problema con il Dreaded Diamond è che la classe base è presente più volte. Quindi, con l'eredità regolare, ritieni di avere:

  A
 / \
B   C
 \ /
  D

Ma nel layout della memoria, hai:

A   A
|   |
B   C
 \ /
  D

Questo spiega perché quando chiami D::foo(), hai un problema di ambiguità. Ma il vero problema si presenta quando si desidera utilizzare una variabile membro di A. Ad esempio, supponiamo di avere:

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

Quando proverai ad accedere m_iValueda D, il compilatore protesterà, perché nella gerarchia ne vedranno due m_iValue, non uno. E se ne modifichi uno, ad esempio B::m_iValue(che è il A::m_iValuegenitore di B), C::m_iValuenon verrà modificato (che è il A::m_iValuegenitore di C).

Qui è dove l'eredità virtuale è utile, così come con essa, tornerai a un vero layout a diamante, con non solo un foo()metodo, ma anche uno e solo uno m_iValue.

Cosa potrebbe andare storto?

Immaginare:

  • A ha alcune funzionalità di base.
  • B aggiunge ad esso una sorta di interessante array di dati (ad esempio)
  • Caggiunge ad esso alcune funzioni interessanti come un modello di osservatore (ad esempio, on m_iValue).
  • Deredita da Be C, e quindi da A.

Con l'eredità normale, la modifica m_iValueda Dè ambigua e questo deve essere risolto. Anche se lo è, ci sono due m_iValuesall'interno D, quindi è meglio ricordare che e aggiornare i due allo stesso tempo.

Con l'eredità virtuale, la modifica m_iValueda Dè ok ... Ma ... Diciamo che hai D. Tramite la sua Cinterfaccia, hai collegato un osservatore. E attraverso la sua Binterfaccia, aggiorni l'array cool, che ha l'effetto collaterale di cambiare direttamente m_iValue...

Poiché il cambiamento di m_iValueviene fatto direttamente (senza usare un metodo di accesso virtuale), l'osservatore che "ascolta" attraverso Cnon verrà chiamato, perché il codice che implementa l'ascolto è dentro Ce Bnon lo sa ...

Conclusione

Se hai un diamante nella tua gerarchia, significa che hai il 95% di probabilità di aver fatto qualcosa di sbagliato in quella gerarchia.


Il tuo "che cosa potrebbe andare storto" è dovuto all'accesso diretto a un membro di base, non a eredità multipla. Sbarazzati di "B" e hai lo stesso problema. Regola di base di: "se non è privato, dovrebbe essere virtuale" evita il problema. M_iValue non è virtuale e quindi dovrebbe essere privato
Chris Dodd,

4
@ Chris Dodd: non esattamente. Quello che succede con m_iValue sarebbe successo a qualsiasi simbolo ( es. Typedef, variabile membro, funzione membro, cast nella classe base, ecc .). Questo è davvero un problema di ereditarietà multipla, un problema che gli utenti dovrebbero essere consapevoli di utilizzare correttamente l'ereditarietà multipla, invece di seguire la strada Java e concludere "L'ereditarietà multipla è dannosa al 100%, facciamolo con le interfacce".
paercebal,

Ciao, quando usiamo una parola chiave virtuale, ci sarà una sola copia di A. La mia domanda è: come facciamo a sapere se proviene da B o C? La mia domanda è valida?
user875036

@ user875036: A proviene sia da B che da C. In effetti, la virtualità cambia alcune cose (ad es. D chiamerà il costruttore di A, non B, né C). Sia B che C (e D) hanno un puntatore ad A.
paercebal,

3
FWIW, nel caso qualcuno si stia chiedendo, le variabili dei membri non possono essere virtuali - virtual è un identificatore di funzioni . SO di riferimento: stackoverflow.com/questions/3698831/...
rholmes

34

Spiegare l'ereditarietà multipla con basi virtuali richiede una conoscenza del modello a oggetti C ++. E spiegare chiaramente l'argomento è meglio farlo in un articolo e non in una casella di commento.

La migliore spiegazione leggibile che ho trovato che ha risolto tutti i miei dubbi su questo argomento è stato questo articolo: http://www.phpcompiler.org/articles/virtualinheritance.html

Non avrai davvero bisogno di leggere nient'altro sull'argomento (a meno che tu non sia uno scrittore di compilatori) dopo aver letto che ...


10

Una classe di base virtuale è una classe che non può essere istanziata: non è possibile creare l'oggetto diretto da essa.

Penso che stai confondendo due cose molto diverse. L'eredità virtuale non è la stessa cosa di una classe astratta. L'ereditarietà virtuale modifica il comportamento delle chiamate di funzione; a volte risolve le chiamate di funzione che altrimenti sarebbero ambigue, a volte difende la gestione delle chiamate di funzione in una classe diversa da quella che ci si aspetterebbe in un'eredità non virtuale.


7

Vorrei aggiungere ai gentili chiarimenti di OJ.

L'eredità virtuale non arriva senza un prezzo. Come per tutte le cose virtuali, ottieni un successo in termini di prestazioni. C'è un modo per aggirare questo colpo di performance che è forse meno elegante.

Invece di rompere il diamante derivando virtualmente, puoi aggiungere un altro strato al diamante, per ottenere qualcosa del genere:

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

Nessuna delle classi eredita virtualmente, tutte ereditano pubblicamente. Le classi D21 e D22 nasconderanno quindi la funzione virtuale f () che è ambigua per DD, forse dichiarando la funzione privata. Ognuno di loro avrebbe definito una funzione wrapper, rispettivamente f1 () e f2 (), ciascuna chiamata class-local (private) f (), risolvendo così i conflitti. La classe DD chiama f1 () se vuole D11 :: f () e f2 () se vuole D12 :: f (). Se si definiscono i wrapper in linea, si otterrà probabilmente un sovraccarico pari a zero.

Naturalmente, se puoi cambiare D11 e D12, puoi fare lo stesso trucco all'interno di queste classi, ma spesso non è così.


2
Non si tratta di questioni più o meno eleganti o di risoluzione delle ambiguità (per questo puoi sempre usare esplicite xxx :: specificazioni). Con l'ereditarietà non virtuale, ogni istanza della classe DD ha due istanze indipendenti di B. Non appena la classe ha un singolo membro di dati non statico, l'ereditarietà virtuale e non virtuale differisce per più della semplice sintassi.
user3489112

@ user3489112 Non appena ... niente. L'eredità virtuale e non virtuale differisce semanticamente, punto.
curioso


1

Stai facendo un po 'di confusione. Non so se stai confondendo alcuni concetti.

Non hai una classe base virtuale nel tuo OP. Hai solo una classe base.

Hai fatto l'eredità virtuale. Questo è di solito usato in eredità multipla in modo che più classi derivate utilizzino i membri della classe base senza riprodurli.

Non è possibile creare un'istanza di una classe base con una funzione virtuale pura. questo richiede la sintassi a cui arriva Paul. In genere viene utilizzato in modo che le classi derivate debbano definire tali funzioni.

Non voglio più spiegare questo perché non capisco perfettamente quello che mi stai chiedendo.


1
Una "classe base" utilizzata in un'eredità virtuale diventa una "classe base virtuale" (nel contesto di quell'eredità precisa).
Luc Hermitte,

1

Significa che una chiamata a una funzione virtuale verrà inoltrata alla classe "giusta".

Domande frequenti su C ++ Lite FTW.

In breve, viene spesso utilizzato in scenari con ereditarietà multipla, in cui si forma una gerarchia "a diamante". L'eredità virtuale interromperà quindi l'ambiguità creata nella classe inferiore, quando si chiama la funzione in quella classe e la funzione deve essere risolta nella classe D1 o D2 sopra quella classe inferiore. Vedi la voce FAQ per un diagramma e dettagli.

Viene anche usato nella delegazione sorella , una funzione potente (anche se non per i deboli di cuore). Vedi questa FAQ.

Vedi anche l'articolo 40 in Effective C ++ 3rd edition (43 in 2nd edition).


1

Esempio di utilizzo eseguibile dell'ereditarietà del diamante

Questo esempio mostra come utilizzare una classe di base virtuale nello scenario tipico: risolvere l'ereditarietà del diamante.

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

2
assert(A::aDefault == 0);dalla funzione principale mi dà un errore di compilazione: aDefault is not a member of Ausando gcc 5.4.0. Cosa dovrebbe fare?
SebNag

@SebTu ah grazie, solo qualcosa che ho dimenticato di rimuovere dalla copia incolla, rimosso ora. L'esempio dovrebbe essere ancora significativo senza di essa.
Ciro Santilli 20 冠状 病 六四 事件 法轮功

0

Le classi virtuali non sono le stesse dell'eredità virtuale. Classi virtuali che non puoi istanziare, l'eredità virtuale è qualcos'altro.

Wikipedia lo descrive meglio di me. http://en.wikipedia.org/wiki/Virtual_inheritance


6
Non esistono "classi virtuali" in C ++. Esistono tuttavia "classi di base virtuali" che sono "virtuali" rispetto a una determinata eredità. Quello che riferisci è ciò che viene ufficialmente chiamato "classi astratte".
Luc Hermitte,

@LucHermitte, ci sono sicuramente classi virtuali in C ++. Controlla questo: en.wikipedia.org/wiki/Virtual_class .
Rafid

"errore: 'virtuale' può essere specificato solo per le funzioni". Non so che lingua sia. Ma non esiste definitivamente una classe virtuale in C ++.
Luc Hermitte,

0

Eredità regolare

Con l'ereditarietà non diamante di 3 livelli tipica non ereditaria, quando si crea un'istanza di un nuovo oggetto più derivato, viene chiamato nuovo e la dimensione richiesta per l'oggetto viene risolta dal tipo di classe dal compilatore e passata a nuova.

nuovo ha una firma:

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

E fa una chiamata a malloc, restituendo il puntatore vuoto

Questo viene quindi passato al costruttore dell'oggetto più derivato, che chiamerà immediatamente il costruttore centrale e quindi il costruttore centrale chiamerà immediatamente il costruttore di base. La base quindi memorizza un puntatore alla sua tabella virtuale all'inizio dell'oggetto e successivamente i suoi attributi. Questo quindi ritorna al costruttore centrale che memorizzerà il suo puntatore di tabella virtuale nella stessa posizione e quindi i suoi attributi dopo gli attributi che sarebbero stati memorizzati dal costruttore di base. Ritorna al costruttore più derivato, che memorizza un puntatore alla sua tabella virtuale nella stessa posizione e quindi i suoi attributi dopo gli attributi che sarebbero stati memorizzati dal costruttore centrale.

Poiché il puntatore alla tabella virtuale viene sovrascritto, il puntatore alla tabella virtuale finisce sempre per essere quello della classe più derivata. La virtualità si propaga verso la classe più derivata, quindi se una funzione è virtuale nella classe media, sarà virtuale nella classe più derivata ma non nella classe base. Se si esegue il cast polimorfico di un'istanza della classe più derivata in un puntatore alla classe base, il compilatore non risolverà questo in una chiamata indiretta alla tabella virtuale e chiamerà invece direttamente la funzione A::function(). Se una funzione è virtuale per il tipo in cui è stata lanciata, si risolverà in una chiamata nella tabella virtuale che sarà sempre quella della classe più derivata. Se non è virtuale per quel tipo, chiamerà Type::function()e passerà il puntatore dell'oggetto, cast su Tipo.

In realtà quando dico puntatore alla sua tabella virtuale, in realtà è sempre un offset di 16 nella tabella virtuale.

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

virtualnon è necessario nuovamente nelle classi più derivate se è virtuale in una classe meno derivata perché si propaga. Ma può essere usato per mostrare che la funzione è effettivamente una funzione virtuale, senza dover controllare le classi che eredita le definizioni dei tipi.

override è un'altra protezione del compilatore che dice che questa funzione ha la precedenza su qualcosa e, in caso contrario, genera un errore del compilatore.

= 0 significa che questa è una funzione astratta

final impedisce che una funzione virtuale venga nuovamente implementata in una classe più derivata e si assicurerà che la tabella virtuale della classe più derivata contenga la funzione finale di quella classe.

= default rende esplicito nella documentazione che il compilatore utilizzerà l'implementazione predefinita

= delete dare un errore del compilatore se si tenta una chiamata a questo

Ereditarietà virtuale

Prendere in considerazione

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

Senza ereditare virtualmente la classe di basso otterrai un oggetto simile al seguente:

Invece di questo:

Cioè ci saranno 2 oggetti base.

Nel diamante situazione eredità virtuale sopra, dopo il nuovo si chiama, si chiama il costruttore più derivati e in quel costruttore, chiama tutti i 3 costruttori derivati che passano gli offset nella sua tabella tabella virtuale, invece di chiamare solo chiamare DerivedClass1::DerivedClass1()e DerivedClass2::DerivedClass2()poi quelli sia chiamataBase::Base()

Quanto segue è tutto compilato in modalità debug -O0, quindi ci sarà un assembly ridondante

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

Chiama Base::Base()con un puntatore all'offset oggetto 32. Base memorizza un puntatore alla sua tabella virtuale all'indirizzo che riceve e ai suoi membri dopo di esso.

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()quindi chiama DerivedClass1::DerivedClass1()con un puntatore all'offset oggetto 0 e passa anche l'indirizzo diVTT 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()poi passa l'indirizzo dell'oggetto + 16 e l'indirizzo della VTT per DerivedDerivedClass+24per DerivedClass2::DerivedClass2()il cui assemblaggio è identico al DerivedClass1::DerivedClass1()eccetto la linea mov DWORD PTR [rax+8], 3che ovviamente ha un 4 invece di 3 per d = 4.

Dopodiché, sostituisce tutti e 3 i puntatori di tabella virtuale nell'oggetto con puntatori agli offset nella tabella di DerivedDerivedClassVtable alla rappresentazione per quella 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.