3

有这个代码:

#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 的用途是什么?它是如何使用的?

4

3 回答 3

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

在这里,B1B2都从 虚拟派生A,所以在 中C,只有一个A子对象。两者都B1需要B2知道如何找到该A子对象(以便他们可以访问成员变量,或者如果我们要定义它们 a的其他成员)。A

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


为了演示编译器如何实现上述菱形继承示例,请考虑以下由 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
        +---

和以下虚表:

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和的B2子对象生成的两个 vtable C,偏移量是不同的。

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

于 2012-01-28T20:04:36.663 回答
1

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

虚拟表其实很简单。首先,每个使用虚函数的类(或派生自使用虚函数的类)都有自己的虚表。该表只是编译器在编译时设置的静态数组。一个虚拟表包含一个条目,对应于类对象可以调用的每个虚拟函数。此表中的每个条目只是一个函数指针,它指向该类可访问的最衍生函数。

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

甚至为具有虚拟基类的类创建虚拟表。在这种情况下,vtable 具有指向基类共享实例的指针以及指向类的虚函数(如果有)的指针。

于 2012-01-28T20:13:05.280 回答
0

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

于 2012-01-28T20:03:55.720 回答