基类中的虚拟继承和空vtable

时间:2012-01-28 19:51:08

标签: c++ vtable virtual-inheritance 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;   
}

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

Derived::ptr to empty vtable
Derived::y
Base::x

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

3 个答案:

答案 0 :(得分:4)

Derived需要某种方式来了解Base子对象的位置。使用虚拟继承时,基类的相对位置不会相对于派生类的位置固定:它可能位于完整对象的任何位置。

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

struct A
{
    int a;
};

struct B1 : virtual A
{
    int b1;
};

struct B2 : virtual A
{
    int b2;
};

struct C : B1, B2
{
    int c;
};

此处,B1B2实际上都来自A,因此在C中,只有一个A子对象。 B1B2都需要知道如何找到A子对象(以便他们可以访问a成员变量或A的其他成员我们要定义它们)。

在这种情况下,这就是vtable的用途:B1B2都有一个vtable,其中包含A子对象的偏移量。


为了演示编译器可能会如何实现上述菱形继承示例,请考虑由Visual C ++ 11 Developer Preview生成的以下类布局和虚拟表。

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
        +---

以及以下vtable:

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)

请注意,偏移量与vtable的地址相关,请注意,对于为B1的{​​{1}}和B2子对象生成的两个vtable,偏移量是不同的。

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

答案 1 :(得分:1)

为了实现虚函数,C ++使用一种特殊形式的后期绑定,称为虚拟表。虚拟表是用于以动态/后期绑定方式解析函数调用的函数的查找表。虚拟表有时会使用其他名称,例如“vtable”,“虚函数表”,“虚方法表”或“调度表”。

虚拟表实际上非常简单。首先,每个使用虚函数的类(或者从使用虚函数的类派生)都被赋予它自己的虚拟表。该表只是编译器在编译时设置的静态数组。虚拟表包含可由类的对象调用的每个虚函数的一个条目。此表中的每个条目只是一个函数指针,指向该类可访问的派生函数最多。

  • 每个使用虚函数的类(或从类派生的类) 它使用虚函数)给它自己的虚拟表作为 秘密数据成员。
  • 此表由编译器在编译时设置。
  • 虚拟表包含一个条目作为每个条目的函数指针 可以由类的对象调用的虚函数。
  • 虚拟表存储指向纯虚函数的NULL指针。

即使对于具有虚拟基类的类,也会创建虚拟表。在这种情况下,vtable具有指向基类的共享实例的指针以及指向classe的虚函数的指针(如果有的话)。

答案 2 :(得分:0)

如果你执行dynamic_cast&lt; Derived *&gt;(ptr_to_obj),它将使用vtable指针来确定ptr_to_obj是否引用了Derived。涉及虚拟方法或继承的每个类都需要一个vtable,并且每个类需要不同,以支持dynamic_cast&lt;&gt;。即使它不包含任何指向方法的指针,它仍然用于识别对象的类型。

相关问题