c++ - 多重虚拟继承中的虚拟表和内存布局

标签 c++ multiple-inheritance vtable virtual-inheritance memory-layout

考虑以下层次结构:

struct A {
   int a; 
   A() { f(0); }
   A(int i) { f(i); }
   virtual void f(int i) { cout << i; }
};
struct B1 : virtual A {
   int b1;
   B1(int i) : A(i) { f(i); }
   virtual void f(int i) { cout << i+10; }
};
struct B2 : virtual A {
   int b2;
   B2(int i) : A(i) { f(i); }
   virtual void f(int i) { cout << i+20; }
};
struct C : B1, virtual B2 {
   int c;
   C() : B1(6),B2(3),A(1){}
   virtual void f(int i) { cout << i+30; }
};
  1. C 实例的确切 内存布局是什么?它包含多少个 vptr,每个 vptr 的确切放置位置?哪些虚拟表与 C 的虚拟表共享?每个虚拟表究竟包含什么?

    我是如何理解布局的:

    ----------------------------------------------------------------
    |vptr1 | AptrOfB1 | b1 | B2ptr | c | vptr2 | AptrOfB2 | b2 | a |
    ----------------------------------------------------------------
    

    其中 AptrOfBx 是指向 Bx 包含的 A 实例的指针(因为继承是虚拟的)。
    那是对的吗? vptr1 指向哪些函数? vptr2 指向哪些函数?

  2. 给定以下代码

    C* c = new C();
    dynamic_cast<B1*>(c)->f(3);
    static_cast<B2*>(c)->f(3);
    reinterpret_cast<B2*>(c)->f(3);
    

    为什么所有对 f 的调用都打印 33

最佳答案

虚拟基地与普通基地有很大不同。请记住,“虚拟”意味着“在运行时确定”——因此整个基础子对象必须在运行时确定。

假设您正在获取 B & x 引用,并且您的任务是查找 A::a 成员。如果继承是真实的,那么 B 有一个父类(super class) A,因此您通过 x< 查看的 B 对象 有一个 A-子对象,您可以在其中找到您的成员 A::a。如果 x 的最衍生对象有多个 A 类型的基,那么您只能看到作为 B 子对象的特定拷贝.

但是如果继承是虚拟的,那么这一切都没有意义。我们不知道我们需要哪个 A-子对象——这些信息在编译时根本不存在。我们可以像在 B y 中那样处理一个实际的 B 对象; B & x = y;,或使用 C-object,如 C z; B & x = z;,或者完全不同的东西,实际上从 A 派生了很多次。唯一知道的方法是在运行时找到实际的基本 A

这可以通过多一层运行时间接来实现。 (请注意,与非虚拟函数相比,这与虚拟 函数 是如何通过额外一层运行时间接实现完全平行的。)一种解决方案不是使用指向 vtable 或基本子对象的指针,而是将指针 存储到指向实际基础子对象的指针。这有时被称为“重击”或“蹦床”。

所以实际的对象 C z; 可能如下所示。内存中的实际排序取决于编译器并且不重要,并且我已经抑制了 vtables。

+-+------++-+------++-----++-----+
|T|  B1  ||T|  B2  ||  C  ||  A  |
+-+------++-+------++-----++-----+
 |         |                 |
 V         V                 ^
 |         |       +-Thunk-+ |
 +--->>----+-->>---|     ->>-+
                   +-------+

因此,无论您有 B1& 还是 B2&,您首先查找 thunk,然后它会告诉您在哪里可以找到实际的基础子对象。这也解释了为什么您不能执行从 A& 到任何派生类型的静态转换:此信息在编译时根本不存在。

如需更深入的解释,请查看 this fine article . (在那个描述中,thunk 是 C 的 vtable 的一部分,并且虚拟继承总是需要维护 vtable,即使任何地方都没有虚拟 函数。)

关于c++ - 多重虚拟继承中的虚拟表和内存布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11603198/

相关文章:

c++ - 如何在 C++ 中动态创建新的二维数组

python-3.x - pyqt5和多重继承

c++ - 是否需要从纯抽象类(接口(interface))进行虚拟继承

c# - C# 中的接口(interface)与多重继承

c++ - 没有多态性的虚拟 C++ 方法

c++ - 如何在 Qt 中取消设置标志?

c++ - 成员函数之外的封闭类的定义中需要默认成员初始值设定项

c# - 将字节数组从 Unity 传递到 Android (C++) 进行修改

c++ - vtable:底层算法

Java方法表