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 malloc
und 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
virtual
wird 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+24
zu DerivedClass2::DerivedClass2()
den Montag identisch ist zu DerivedClass1::DerivedClass1()
der Linie ausnehmen , mov DWORD PTR [rax+8], 3
die offensichtlich ein 4 anstelle von 3 für besitzt d = 4
.
Danach werden alle 3 virtuellen Tabellenzeiger im Objekt durch Zeiger auf Offsets in DerivedDerivedClass
der 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