c – 虚拟表和多个虚拟继承中的内存布局

考虑以下层次:
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; }
};

> C实例的内存布局是什么?它包含几个vptrs,其中每个都放置在哪里?哪些虚拟表与C的虚拟表共享?每个虚拟表包含什么?

这里我怎么理解布局:

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

其中AptrOfBx是指向Bx包含的实例的指针(因为继承是虚拟的).
那是对的吗?哪个功能vptr1指向?哪个功能vptr2指向?
>给出以下代码

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具有超类A,因此通过x查看的B对象具有A子对象,您可以在其中找到您的成员A :: a.如果x的派生最多的对象具有多个类型A的基数,那么您只能看到作为B的子对象的特定副本.

但是,如果继承是虚拟的,这些都不是有意义的.我们不知道我们需要哪个A子对象 – 这个信息在编译时根本不存在.我们可以像B一样处理一个实际的B对象; B& x = y,或与C z对应的C对象; B& x = z;或完全不同的东西,几乎从多个派生出来.唯一的知道方法是在运行时找到实际的基数A.

这可以通过一个更多级别的运行时间间隔来实现. (注意,与非虚函数相比,这与虚拟函数如何实现一个额外的运行时间间隔完全相同).而不是有一个指向vtable或者基本子对象的指针,一个解决方案是存储指向指针的指针到实际的子对象.这有时被称为“笨蛋”或“蹦床”.

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

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

因此,无论你是否有B1&或者B2和amp,你首先查找thunk,那个又反过来告诉你在哪里找到实际的基本子对象.这也解释了为什么你不能从A&到任何派生类型:这个信息在编译时根本不存在.

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

相关文章

本程序的编译和运行环境如下(如果有运行方面的问题欢迎在评...
水了一学期的院选修,万万没想到期末考试还有比较硬核的编程...
补充一下,先前文章末尾给出的下载链接的完整代码含有部分C&...
思路如标题所说采用模N取余法,难点是这个除法过程如何实现。...
本篇博客有更新!!!更新后效果图如下: 文章末尾的完整代码...
刚开始学习模块化程序设计时,估计大家都被形参和实参搞迷糊...