虚继承自基础结构体

3
struct A {
int i;
virtual void f() { cout << i; }
A() { i = 1; }
A(int _i) : i(_i) {}
};

struct B : A {
B() : A(2) { f(); }
void f() { cout << i+10; }
};

struct C : B, virtual A {
C() : A() {}
};

请解释为什么在C结构中会有两个A::i,以及为什么在C结构中会有两个vptr指向“虚拟表方法”,尽管A只有一个虚拟方法?我知道当有共同的基类进行虚拟继承时,会有一个共同的基类实例而不是两个,敬请指教!

2个回答

17

没有虚拟
当不使用virtual进行继承时,您可以将内存结构想象成这样:

class B : A {};

Inheritance without virtual

A的变量位于(就在上面)类B的变量之内。

使用虚拟继承
当使用virtual进行继承时,B只是拥有一个指向A的"指针":

class B : virtual A {};

Inheritance with virtual

你的例子
这意味着,你的例子看起来像这样:

class B : A
class C : B, virtual A

Your example

只有一个实例的 A 如果你只想要一个 A 的实例,你必须使用两次 virtual

class B : virtual A
class C : B, virtual A

Only one instance of A

vptr 的

这是您的代码布局,由 g++ 生成(使用 -fdump-class-hierarchy 生成):

Class C
   size=16 align=4
   base size=8 base align=4
C (0xb7193440) 0
    vptridx=0u vptr=((& C::_ZTV1C) + 12u)
  B (0xb719f078) 0
      primary-for C (0xb7193440)
    A (0xb719a428) 0
        primary-for B (0xb719f078)
  A (0xb719a460) 8 virtual
      vptridx=4u vbaseoffset=-12 vptr=((& C::_ZTV1C) + 28u)

我对vpointers和vtables有点生疏,所以我不确定为什么会有那些v-pointers。

然而,我可以告诉你,你的假设只有一个虚方法是错误的。 因为B继承自A,并且A::f()是虚拟的,派生的B::f()也自动成为虚拟的,即使你没有明确写下这一点。

编辑:

经过一番挖掘,我想我记得哪些v-pointers是必需的,哪些不是。 但是我不能保证以下内容。
在下面,符号C.B.A表示在C中的B子对象中的A子对象,以区分两个A子对象(C.B.AC.A)。

每个子类都需要一个v指针。然而,*(C.B.A)、*(C.B)和*C都指向同一位置(即类C的开头,也是C.B和C.B.A的开头),因此它们可以共享一个v指针。但是,指向子类*(C.A)的指针将指向不同的位置,因此还需要另一个v指针。另外,请注意,子类C.B.A在您的示例中甚至无法访问(gcc:"warning: direct base ‘A’ inaccessible in ‘C’ due to ambiguity")。
为了使其更清晰一些:如果你只有以下结构
class B : A {};
class C : B {};

只需要一个虚指针,因为指向任何C子类的指针都将指向同一位置。虚指针只需指向ABC中的任意一个的虚表,即可告知对象的运行时类型。


谢谢你清晰的解释,但是关于问题的最后一部分,有多少个vptr(指向虚方法表的指针),它们在哪里? - nabil

1

B没有继承虚拟的方式。

所以你有从B继承的A结构和另一个虚拟继承的结构。


那么C会包含两个A吗?在这种情况下,当我们执行以下操作时: C* c = new C(); c->f(); 哪个f将会运行?B::f还是A::f? - nabil
@nabil,编译器可能会抛出一个错误,告诉你它是模棱两可的。 - Yochai Timmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接