在c++中,什么是虚基类?

我想知道“虚基类”是什么,它意味着什么。

让我举个例子:

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


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

这意味着对虚函数的调用将被转发到“正确的”类。

c++ __abc0 ftw。

简而言之,它通常用于多继承场景,其中形成了“菱形”层次结构。虚继承将打破在底层类中创建的模糊性,当您调用该类中的函数并且函数需要解析到底层类之上的类D1或D2时。有关图表和细节,请参阅FAQ条目

它也在妹妹代表团中使用,这是一个强大的功能(尽管不适合胆小的人)。参见 FAQ。

参见Effective c++第3版中的第40项(第2版中的43项)。

虚拟基类用于虚拟继承,是一种使用多重继承时防止在继承层次结构中出现给定类的多个“实例”的方法。

考虑以下场景:

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

上面的类层次结构导致了“可怕的钻石”,看起来像这样:

  A
/ \
B   C
\ /
D

D的一个实例将由B和C组成,B包含A, C也包含A。所以你有两个A的“实例”(为了更好的表达)。

在这种情况下,就有可能出现模棱两可的情况。当你这样做时会发生什么:

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

虚拟继承可以解决这个问题。当您在继承类时指定virtual时,您是在告诉编译器您只需要一个实例。

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

这意味着在层次结构中只包含A的一个“实例”。因此

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

这是一个小总结。有关更多信息,请阅读。还有一个很好的例子在这里

虚基类是一个类 不能被实例化:你不能

我认为你混淆了两件完全不同的事情。虚拟继承与抽象类不是一回事。虚继承修改函数调用的行为;有时它会解析否则会有歧义的函数调用,有时它将函数调用处理推迟到非虚继承中期望的类。

你有点让人困惑了。我不知道你是否混淆了一些概念。

op中没有虚基类,只有基类。

你做了虚拟继承。这通常用于多重继承,以便多个派生类使用基类的成员而不复制它们。

不实例化具有纯虚函数的基类。这需要Paul掌握的语法。通常使用它是为了派生类必须定义这些函数。

我不想再解释了,因为我不太明白你在问什么。

虚类与虚继承相同。虚拟类你不能实例化,虚拟继承完全是另一回事。

维基百科描述得比我好。http://en.wikipedia.org/wiki/Virtual_inheritance

我想对OJ的善意澄清补充一点。

虚拟继承是有代价的。就像所有虚拟的东西一样,你的性能会受到影响。有一种方法可以绕过这种性能冲击,但可能不那么优雅。

你可以在菱形中添加另一层,而不是通过虚拟派生来打破菱形,以得到如下内容:

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

没有一个类是虚继承的,所有类都是公开继承的。类D21和D22将隐藏虚函数f(),这对于DD来说是模糊的,可能是通过将函数声明为private。它们将分别定义一个包装器函数f1()和f2(),分别调用class-local (private) f(),从而解决冲突。类DD在需要D11::f()时调用f1(),在需要D12::f()时调用f2()。如果内联定义包装器,可能会得到零开销。

当然,如果您可以更改D11和D12,那么您可以在这些类中执行相同的技巧,但通常情况下并非如此。

关于内存布局

作为旁注,可怕的钻石的问题是基类出现了多次。通过常规遗传,你相信你有:

  A
/ \
B   C
\ /
D

但是在内存布局中,你有:

A   A
|   |
B   C
\ /
D

这解释了为什么当调用D::foo()时,你会有一个歧义问题。但是,当你想使用A的成员变量时,就会出现真正的问题。例如,假设我们有:

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

当你试图从D访问m_iValue时,编译器会抗议,因为在层次结构中,它会看到两个m_iValue,而不是一个。如果你修改了一个,比如B::m_iValue(那是BA::m_iValue父结点),C::m_iValue将不会被修改(那是CA::m_iValue父结点)。

这就是虚拟继承方便的地方,有了它,你会回到一个真正的菱形布局,不仅只有一个foo()方法,而且还有一个且只有一个m_iValue方法。

会出什么问题呢?

想象一下:

  • A有一些基本的特性。
  • B添加了一些很酷的数据数组(例如)
  • C为它添加了一些很酷的特性,比如观察者模式(例如,在m_iValue上)。
  • D继承自BC,因此继承自A

对于普通继承,从D修改m_iValue是不明确的,必须解决这个问题。即使是,在D中也有两个m_iValues,所以你最好记住这一点,并同时更新这两个。

使用虚拟继承,从D修改m_iValue是可以的…但是…假设你有D。通过它的C接口,你附加了一个观察者。并且通过它的B接口,你更新了这个酷数组,它的副作用是直接改变m_iValue

由于m_iValue的更改是直接完成的(不使用虚拟访问器方法),观察者“监听”;通过C将不会被调用,因为实现监听的代码在C中,而B不知道它…

结论

如果你的层次结构中有一颗钻石,这意味着你有95%的概率在该层次结构中做了错事。

除了前面提到的多重继承和虚拟继承之外,Dr Dobb’s Journal上还有一篇非常有趣的文章:多重继承被认为是有用的

解释使用虚拟基的多重继承需要c++对象模型的知识。最好是在一篇文章中,而不是在评论框中,清楚地解释这个主题。

我发现最好的,易读的解释,解决了我对这个问题的所有怀疑是这篇文章:http://www.phpcompiler.org/articles/virtualinheritance.html

在读完这篇文章之后,你真的不需要再阅读关于这个主题的任何其他内容了(除非你是编译器作者)…

钻石继承可运行的用法示例

这个示例展示了如何在典型场景中使用虚拟基类:解决菱形继承问题。

考虑下面的工作示例:

main.cpp

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

编译并运行:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

如果我们将virtual移到:

class B : public virtual A

我们会得到一堵关于GCC无法解析D成员和通过a继承两次的方法的错误墙:

main.cpp:27:7: warning: virtual base ‘A’ inaccessible in ‘D’ due to ambiguity [-Wextra]
27 | class D : public B, public C {
|       ^
main.cpp: In member function ‘virtual int D::h()’:
main.cpp:30:40: error: request for member ‘i’ is ambiguous
30 |         virtual int h() { return this->i + this->j + this->k; }
|                                        ^
main.cpp:7:13: note: candidates are: ‘int A::i’
7 |         int i;
|             ^
main.cpp:7:13: note:                 ‘int A::i’
main.cpp: In function ‘int main()’:
main.cpp:34:20: error: invalid cast to abstract class type ‘D’
34 |     D d = D(1, 2, 4);
|                    ^
main.cpp:27:7: note:   because the following virtual functions are pure within ‘D’:
27 | class D : public B, public C {
|       ^
main.cpp:8:21: note:    ‘virtual int A::f()’
8 |         virtual int f() = 0;
|                     ^
main.cpp:9:21: note:    ‘virtual int A::g()’
9 |         virtual int g() = 0;
|                     ^
main.cpp:34:7: error: cannot declare variable ‘d’ to be of abstract type ‘D’
34 |     D d = D(1, 2, 4);
|       ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:35:14: error: request for member ‘f’ is ambiguous
35 |     assert(d.f() == 3);
|              ^
main.cpp:8:21: note: candidates are: ‘virtual int A::f()’
8 |         virtual int f() = 0;
|                     ^
main.cpp:17:21: note:                 ‘virtual int B::f()’
17 |         virtual int f() { return this->i + this->j; }
|                     ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:36:14: error: request for member ‘g’ is ambiguous
36 |     assert(d.g() == 5);
|              ^
main.cpp:9:21: note: candidates are: ‘virtual int A::g()’
9 |         virtual int g() = 0;
|                     ^
main.cpp:24:21: note:                 ‘virtual int C::g()’
24 |         virtual int g() { return this->i + this->k; }
|                     ^
main.cpp:9:21: note:                 ‘virtual int A::g()’
9 |         virtual int g() = 0;
|                     ^
./main.out

在GCC 9.3.0, Ubuntu 20.04上测试。

常规的继承

对于典型的3级非菱形非虚拟继承,当你实例化一个新的最派生对象时,new被调用,堆上对象所需的大小由编译器从类类型解析并传递给new。

new有一个签名:

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

并调用malloc,返回空指针

然后将此地址传递给最派生对象的构造函数,该构造函数将立即调用中间构造函数,然后中间构造函数将立即调用基本构造函数。然后,基类在对象的开头存储指向其虚拟表的指针,然后在对象的后面存储指向其属性的指针。然后返回到中间构造函数,中间构造函数将它的虚表指针存储在相同的位置,然后将它的属性存储在基本构造函数存储的属性之后。然后返回最派生的构造函数,该构造函数将指向其虚拟表的指针存储在同一位置,然后将其属性存储在中间构造函数存储的属性之后。

因为虚表指针被重写,所以虚表指针最终总是派生最多的类之一。虚性向派生最多的类传播,因此如果一个函数在中间类中是虚的,那么它在派生最多的类中也是虚的,但在基类中不是。如果将最派生类的实例多态转换为指向基类的指针,则编译器不会将其解析为对虚表的间接调用,而是直接调用函数A::function()。如果一个函数对于强制转换的类型是虚的,那么它将解析为对虚表的调用,而虚表将始终是最派生类的虚表。如果它不是该类型的虚值,则只调用Type::function()并将对象指针传递给它,转换为type。

实际上,当我说指针指向它的虚表时,它实际上总是向虚表偏移16。

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在派生较多的类中为虚值,则在派生较少的类中不再需要,因为它沿派生最多的类的方向向下传播。但是它可以用来显示函数确实是一个虚函数,而不必检查它继承的类的类型定义。当函数声明为虚函数时,从那时起,只使用继承链中的最后一个实现,但在此之前,如果对象被强制转换为定义该方法的继承链中该类的类型,则仍然可以使用非虚实现。它可以在该名称和签名的方法开始之前在链中它之前的多个类中定义非虚拟的,并且它们在引用时将使用自己的方法(并且链中该定义之后的所有类如果没有自己的定义,将使用该定义,而与此相反,虚类总是使用最终定义)。当一个方法被声明为虚方法时,它必须在该类中实现,或者在为使用而构造的完整对象的继承链中更派生的类中实现。

override是另一个编译器保护,它表示该函数覆盖了某些内容,如果没有,则抛出编译器错误。

= 0表示这是一个抽象函数

final防止在派生较多的类中再次实现虚函数,并将确保派生最多的类的虚表包含该类的最终函数。

= default在文档中明确表示编译器将使用默认实现

如果尝试调用= delete,则会给出编译器错误

如果调用非虚函数,它将解析为正确的方法定义,而不需要通过虚表。如果你调用一个在继承类中有最终定义的虚函数,那么它将使用它的虚表,如果你在调用方法时没有将对象指针强制转换为该类型,它将自动将子对象传递给它。如果在该类型的指针上调用最派生类中定义的虚函数,那么它将使用它的虚表,即位于对象开头的虚表。如果你称之为一种遗传类型和函数的指针也是虚拟,班上就会使用这个子对象的虚表指针,在第一子对象的情况下将最派生类指针一样,它不会包含铛作为对象,子对象的地址是相同的,因此它只是简单的方法自动重铸这个指针,但是在二子对象的情况下,其虚表将包含一个非虚拟铛将继承类型的对象的指针类型最派生类中的实现预期,这是完整的对象,因此抵消了子对象指针指向对象,基子对象的情况下,将需要一个虚拟铛来抵消基本完整的对象的指针,这样它可以重塑的方法隐藏对象参数类型。

使用带有引用操作符而不是通过指针(解引用操作符)的对象打破了多态性,将虚拟方法视为常规方法。这是因为由于切片,不能对非指针类型进行多态强制转换。

虚拟继承

考虑

class Base
{
int a = 1;
int b = 2;
public:
void virtual CommonFunction(){} ; //define empty method body
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();
}

在不实际继承bass类的情况下,你将得到一个如下所示的对象:

而不是这样:

也就是说,会有2个基本对象。

new之后,上面的钻石在虚拟继承的情况,它的地址空间分配的对象最DerivedDerivedClass::DerivedDerivedClass()派生构造函数,调用Base::Base()第一,写其vtable基地的专门的子对象,然后DerivedClass1::DerivedClass1() DerivedDerivedClass::DerivedDerivedClass()电话,写其虚表指针指向它的子对象以及覆盖基本子对象的指针的对象通过咨询VTT传递,然后调用DerivedClass1::DerivedClass1()做同样的事情,最后DerivedDerivedClass::DerivedDerivedClass()用继承类的虚表指针覆盖所有3个指针。这代替了(如上面的第一张图所示)DerivedDerivedClass::DerivedDerivedClass()调用DerivedClass1::DerivedClass1(),调用Base::Base()(覆盖虚拟指针),返回,将地址偏移到下一个子对象,调用DerivedDerivedClass::DerivedDerivedClass()0,然后也调用Base::Base(),覆盖虚拟指针,返回,然后DerivedDerivedClass::DerivedDerivedClass()2构造函数用它的虚拟表指针覆盖两个虚拟指针(在这种情况下,最派生构造函数的虚拟表包含2个子表而不是3个子表)。

以下代码都是在调试模式-O0下编译的,因此会有冗余的程序集

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 the d pointer variable on -O0, will be optimised off on -Ofast if the address of the pointer itself isn't taken in the code, because this address does not need to be on the stack, it can just be passed in a register to the subsequent methods

顺便说一句,如果代码是DerivedDerivedClass d = DerivedDerivedClass()main函数将看起来像这样:

main:
push    rbp
mov     rbp, rsp
sub     rsp, 48 // make room for and zero 48 bytes on the stack for the 48 byte object, no extra padding required as the frame is 64 bytes with `rbp` and return address of the function it calls (no stack params are passed to any function it calls), hence rsp will be aligned by 16 assuming it was aligned at the start of this frame
mov     QWORD PTR [rbp-48], 0
mov     QWORD PTR [rbp-40], 0
mov     QWORD PTR [rbp-32], 0
mov     QWORD PTR [rbp-24], 0
mov     QWORD PTR [rbp-16], 0
mov     QWORD PTR [rbp-8], 0
lea     rax, [rbp-48] // load the address of the cleared 48 bytes
mov     rdi, rax // pass the address as a pointer to the 48 bytes cleared as the first parameter to the constructor
call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]
//address is not stored on the stack because the object is used directly -- there is no pointer variable -- d refers to the object on the stack as opposed to being a pointer

回到最初的例子,DerivedDerivedClass构造函数:

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 (DerivedClass1 subobject)
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 DerivedClass2 subobject 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 // stores e = 5 in the object
.LBE5:
nop
leave
ret

DerivedDerivedClass构造函数使用指向偏移量为32的对象的指针调用Base::Base()。Base在它接收到的地址处存储一个指向它的虚表的指针,并在它之后存储它的成员。

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()然后用指向偏移量为0的对象的指针调用DerivedClass1::DerivedClass1(),并传递VTT 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]   //derference VTT+8+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 //(DerivedClass1 uses this to write its vtable pointer)
.quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+72 //(DerivedClass1 uses this to overwrite the base vtable pointer)
.quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+24
.quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+72
.quad   vtable for DerivedDerivedClass+120 // DerivedDerivedClass supposed to use this to overwrite Bases's vtable pointer
.quad   vtable for DerivedDerivedClass+72 // DerivedDerivedClass supposed to use this to overwrite DerivedClass2's vtable pointer
//although DerivedDerivedClass uses vtable for DerivedDerivedClass+72 and DerivedDerivedClass+120 directly to overwrite them instead of going through the VTT


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有一个虚表,每个虚表都有一个子表,它使用指向子表的指针来覆盖继承类的构造函数为每个子对象存储的构造虚表指针。每个需要thunk的虚方法(虚thunk将对象指针从基对象偏移到对象的开始,非虚thunk将对象指针从继承类的非基对象对象偏移到类型为DerivedDerivedClass的整个对象的开始)。DerivedDerivedClass构造函数还使用虚表表(VTT)作为它需要使用的所有虚表指针的串行列表,并将其传递给每个构造函数(以及构造函数所针对的子对象地址),它们使用该子对象地址来覆盖它们和基类的虚表指针。

DerivedDerivedClass::DerivedDerivedClass()然后将对象的地址+16和DerivedDerivedClass+24的VTT地址传递给DerivedClass2::DerivedClass2(),后者的程序集与DerivedClass1::DerivedClass1()相同,除了mov DWORD PTR [rax+8], 3行,其中d = 4显然是4而不是3。

在此之后,它将对象中的所有3个虚表指针替换为指向DerivedDerivedClass虚表中该类表示的偏移量的指针。

main中调用d->VirtualFunction():

        mov     rax, QWORD PTR [rbp-24] //store pointer to object (and hence vtable pointer) in rax
mov     rax, QWORD PTR [rax] //dereference this pointer to vtable pointer and store virtual table pointer in rax
add     rax, 8 // add 8 to the pointer to get the 2nd function pointer in the table
mov     rdx, QWORD PTR [rax] //dereference this pointer to get the address of the method to call
mov     rax, QWORD PTR [rbp-24] //restore pointer to object in rax (-O0 is inefficient, yes)
mov     rdi, rax  //pass object to the method
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  //pass object to method
call    rdx  //call the first function in the table

d->DerivedCommonFunction2();:

        mov     rax, QWORD PTR [rbp-24] //get the object pointer
lea     rdx, [rax+16]  //get the address of the 2nd subobject in the object
mov     rax, QWORD PTR [rbp-24] //get the object pointer
mov     rax, QWORD PTR [rax+16] // get the vtable pointer of the 2nd subobject
add     rax, 8  //call the 2nd function in this table
mov     rax, QWORD PTR [rax]  //get the address of the 2nd function
mov     rdi, rdx  //call it and pass the 2nd subobject to it
call    rax

d->DerivedDerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24] //get the object pointer
mov     rax, QWORD PTR [rax] //get the vtable pointer
add     rax, 16 //get the 3rd function in the first virtual table (which is where virtual functions that that first appear in the most derived class go, because they belong to the full object which uses the virtual table pointer at the start of the object)
mov     rdx, QWORD PTR [rax] //get the address of the object
mov     rax, QWORD PTR [rbp-24]
mov     rdi, rax  //call it and pass the whole object to it
call    rdx

((DerivedClass2*)d)->DerivedCommonFunction2();:

//it casts the object to its subobject and calls the corresponding method in its virtual table, which will be a non-virtual thunk


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

//it casts the object to its subobject and calls the corresponding function in its virtual table, which will be a virtual thunk


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