基类中的虚拟继承和空 vtable

发布于 2024-12-29 16:19:45 字数 454 浏览 2 评论 0原文

有这样的代码:

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

我读到,当某个类被虚拟继承时,就会为派生类创建空的 vtable,所以内存布局如下:

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

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

There is this code:

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

I have read that when some class is virtually inherited then there is created empty vtable for class Derived, so memory layout is as follows:

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

and it is 12 bytes. The question is - what is purpose of this empty vtable if there are not any virtual methods and how is it used?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

眼前雾蒙蒙 2025-01-05 16:19:45

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 中,只有一个 一个子对象。 B1B2 都需要知道如何找到 A 子对象(以便它们可以访问 a 成员变量,或者 A 的其他成员(如果我们要定义它们)。

这就是 vtable 在本例中的用途:B1B2 都会有一个 vtable,其中包含 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
        +---

以及以下 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 的地址而言的,并且请注意,对于为 B1B2 子对象生成的两个 vtable C,偏移量不同。

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

Derived needs some way to know where the Base subobject is. With virtual inheritance, the relative location of the base class is not fixed with respect to the location of the derived class: it may be located anywhere in the full object.

Consider a more typical example involving diamond inheritance.

struct A
{
    int a;
};

struct B1 : virtual A
{
    int b1;
};

struct B2 : virtual A
{
    int b2;
};

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

Here, both B1 and B2 derive virtually from A, so in C, there is exactly one A subobject. Both B1 and B2 need to know how to find that A subobject (so that they can access the a member variable, or other members of A if we were to define them).

This is what the vtable is used for in this case: both B1 and B2 will have a vtable that contains the offset of the A subobject.


To demonstrate what a compiler might do to implement the above diamond inheritance example, consider the following class layouts and virtual tables, generated by the 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
        +---

and the following vtables:

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)

Note that the offsets are relative to the address of the vtable, and note that for the two vtables generated for the B1 and B2 subobjects of C, the offsets are different.

(Also note that this is entirely an implementation detail--other compilers may implement virtual functions and bases differently. This example demonstrates one way that they are implemented, and they are very commonly implemented this way.)

入画浅相思 2025-01-05 16:19:45

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

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

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

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

To implement virtual functions, C++ uses a special form of late binding known as the virtual table. The virtual table is a lookup table of functions used to resolve function calls in a dynamic/late binding manner. The virtual table sometimes goes by other names, such as “vtable”, “virtual function table”, “virtual method table”, or “dispatch table”.

The virtual table is actually quite simple. First, every class that uses virtual functions (or is derived from a class that uses virtual functions) is given it’s own virtual table. This table is simply a static array that the compiler sets up at compile time. A virtual table contains one entry for each virtual function that can be called by objects of the class. Each entry in this table is simply a function pointer that points to the most-derived function accessible by that class.

  • Every class that uses virtual functions (or is derived from a class
    that uses virtual functions) is given it's own virtual table as a
    secret data member.
  • This table is set up by the compiler at compile time.
  • A virtual table contains one entry as a function pointer for each
    virtual function that can be called by objects of the class.
  • Virtual table stores NULL pointer to pure virtual functions.

Virtual Table is created even for classes that have virtual base classes. In this case, the vtable has pointer to the shared instance of the base class along with the pointers to the classe's virtual functions if any.

染柒℉ 2025-01-05 16:19:45

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

If you do dynamic_cast<Derived*>(ptr_to_obj), it will use the vtable pointer to determine whether ptr_to_obj refers to a Derived or not. Every class which is involved with virtual methods or inheritance needs a vtable, and it needs to be distinct for each class to support dynamic_cast<>. Even if it doesn't contain any pointers to methods, it is still used to identify the type of the object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文