基类中的虚拟继承和空虚表

scd*_*dmb 3 c++ virtual-inheritance vtable memory-layout vptr

有这个代码:

#include <iostream>

class Base
{
   int x;
};

class Derived : virtual public Base
{
   int y;
};

int main()
{
    std::cout << sizeof(Derived) << std::endl; // prints 12
    return 0;   
}
Run Code Online (Sandbox Code Playgroud)

我已经读过,当某个类被虚拟继承时,会为类派生创建空虚表,因此内存布局如下:

Derived::ptr to empty vtable
Derived::y
Base::x
Run Code Online (Sandbox Code Playgroud)

它是 12 个字节。问题是 -如果没有任何虚拟方法,这个空的vtable 的目的是什么,它是如何使用的?

Jam*_*lis 5

Derived需要某种方式来知道Base子对象在哪里。对于虚拟继承,基类的相对位置相对于派生类的位置不是固定的:它可以位于完整对象中的任何位置。

考虑一个更典型的例子,涉及钻石继承。

struct A
{
    int a;
};

struct B1 : virtual A
{
    int b1;
};

struct B2 : virtual A
{
    int b2;
};

struct C : B1, B2
{
    int c;
};
Run Code Online (Sandbox Code Playgroud)

在这里,B1B2都虚拟地派生自A,因此在 中C,恰好只有一个A子对象。双方B1B2需要知道如何找到A子对象(以便他们可以访问a成员变量,或其他成员A如果我们定义它们)。

这就是虚函数表被用于在这种情况下:既B1B2将包含的偏移的虚函数表A的子对象。


为了演示编译器如何实现上述菱形继承示例,请考虑以下由 Visual C++ 11 开发人员预览版生成的类布局和虚拟表。

class A size(4):
        +---
 0      | a
        +---

class B1        size(12):
        +---
 0      | {vbptr}
 4      | b1
        +---
        +--- (virtual base A)
 8      | a
        +---

class B2        size(12):
        +---
 0      | {vbptr}
 4      | b2
        +---
        +--- (virtual base A)
 8      | a
        +---

class C size(24):
        +---
        | +--- (base class B1)
 0      | | {vbptr}
 4      | | b1
        | +---
        | +--- (base class B2)
 8      | | {vbptr}
12      | | b2
        | +---
16      | c
        +---
        +--- (virtual base A)
20      | a
        +---
Run Code Online (Sandbox Code Playgroud)

以及以下虚拟表:

B1::$vbtable@:
 0      | 0
 1      | 8 (B1d(B1+0)A)

B2::$vbtable@:
 0      | 0
 1      | 8 (B2d(B2+0)A)

C::$vbtable@B1@:
 0      | 0
 1      | 20 (Cd(B1+0)A)

C::$vbtable@B2@:
 0      | 0
 1      | 12 (Cd(B2+0)A)
Run Code Online (Sandbox Code Playgroud)

注意偏移量是相对于vtable的地址而言的,注意对于为B1B2子对象生成的两个vtable C,偏移量是不同的。

(另请注意,这完全是一个实现细节——其他编译器可能会以不同的方式实现虚函数和基函数。此示例演示了它们的一种实现方式,并且它们非常普遍地以这种方式实现。)