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() { /* ... */ }
};
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() { /* ... */ }
};
Risposte:
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 .
virtual
, il layout dell'oggetto sembra il diamante; e se non lo utilizziamo, virtual
il layout dell'oggetto sembra una struttura ad albero che contiene due A
secondi
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_iValue
da 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_iValue
genitore di B
), C::m_iValue
non verrà modificato (che è il A::m_iValue
genitore 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
.
Immaginare:
A
ha alcune funzionalità di base.B
aggiunge ad esso una sorta di interessante array di dati (ad esempio)C
aggiunge ad esso alcune funzioni interessanti come un modello di osservatore (ad esempio, on m_iValue
).D
eredita da B
e C
, e quindi da A
.Con l'eredità normale, la modifica m_iValue
da D
è ambigua e questo deve essere risolto. Anche se lo è, ci sono due m_iValues
all'interno D
, quindi è meglio ricordare che e aggiornare i due allo stesso tempo.
Con l'eredità virtuale, la modifica m_iValue
da D
è ok ... Ma ... Diciamo che hai D
. Tramite la sua C
interfaccia, hai collegato un osservatore. E attraverso la sua B
interfaccia, aggiorni l'array cool, che ha l'effetto collaterale di cambiare direttamente m_iValue
...
Poiché il cambiamento di m_iValue
viene fatto direttamente (senza usare un metodo di accesso virtuale), l'osservatore che "ascolta" attraverso C
non verrà chiamato, perché il codice che implementa l'ascolto è dentro C
e B
non lo sa ...
Se hai un diamante nella tua gerarchia, significa che hai il 95% di probabilità di aver fatto qualcosa di sbagliato in quella gerarchia.
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 ...
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.
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ì.
Oltre a ciò che è già stato detto su eredità multipla e virtuale, c'è un articolo molto interessante sul Journal del dottor Dobb: l' ereditarietà multipla considerata utile
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.
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).
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);
}
assert(A::aDefault == 0);
dalla funzione principale mi dà un errore di compilazione: aDefault is not a member of A
usando gcc 5.4.0. Cosa dovrebbe fare?
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
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
virtual
non è 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
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+24
per DerivedClass2::DerivedClass2()
il cui assemblaggio è identico al DerivedClass1::DerivedClass1()
eccetto la linea mov DWORD PTR [rax+8], 3
che 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 DerivedDerivedClass
Vtable 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