为什么必须由派生类最多的类构造虚拟基类?

眠りネ*_*ネロク 19 c++ inheritance virtual-inheritance

以下代码将无法编译:

class A {
public:
    A(int) {}
};

class B: virtual public A {
public:
    B(): A(0) {}
};

// most derived class
class C: public B {
public:
    C() {} // wrong!!!
};
Run Code Online (Sandbox Code Playgroud)

如果我AC构造函数初始化列表中调用构造函数,那就是:

// most derived class
class C: public B {
public:
    C(): A(0) {} // OK!!!
};
Run Code Online (Sandbox Code Playgroud)

它确实有效.

显然,原因是因为虚拟基类必须始终由最派生的类构造.

我不明白这个限制背后的原因.

Cal*_*eth 26

因为它避免了这个:

class A {
public:
    A(int) {}
};

class B0: virtual public A {
public:
    B0(): A(0) {}
};

class B1: virtual public A {
public:
    B1(): A(1) {}
};

class C: public B0, public B1 {
public:
    C() {} // How is A constructed? A(0) from B0 or A(1) from B1?
};
Run Code Online (Sandbox Code Playgroud)

  • 着名的钻石问题 (17认同)
  • @ PeterA.Schneider如果其中一个构造函数是在不同的转换单元中定义的,则编译器无法检测到它.检测也与修复不同.如果你不能修改`B0`或'B1`,你会如何解决这个例子中的歧义? (8认同)
  • @ PeterA.Schneider让我们假设你不需要C来定义它在某些情况下如何构造A,你在哪里画线?什么时候只有一个基地?只有当B0和B1都使用constexpr值时,它们是相同的值?只有当B0和B1使用相同的文字时? (2认同)

Aja*_*jay 8

因为在具有虚拟继承的基类的类层次结构中,基类将/可以由多个类共享(例如,在菱形继承中,其中相同的基类由多个类继承).这意味着,只有一个虚拟继承的基类副本.它本质上意味着,必须首先构造基类.它最终意味着派生类必须实例化给定的基类.

例如:

class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2 // A is shared, and would have one copy only.
Run Code Online (Sandbox Code Playgroud)


Pet*_*ica 5

我发现这条规则容易出错而且很麻烦(但是,多重继承的哪一部分不是?)。

但是逻辑上强加的构造顺序必须不同于正常(非虚拟)继承的情况。考虑 Ajay 的例子,减去虚拟:

class A;
class B1 : A;
class B2 : A;
class C: B1,B2
Run Code Online (Sandbox Code Playgroud)

在这种情况下,对于每个 C,构造了两个 As,一个作为 B1 的一部分,另一个作为 B2 的一部分。B类的代码负责,并且可以做到。事件的顺序是:

Start C ctor
   Start B1 ctor
      A ctor (in B's ctor code)
   End B1 ctor
   Start B2 ctor
      A ctor (in B's ctor code)
   End B2 ctor
End C ctor
Run Code Online (Sandbox Code Playgroud)

现在考虑虚拟继承

class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2 
Run Code Online (Sandbox Code Playgroud)

事件的一种顺序是

Start C ctor
   A ctor // not B's code!
   Start B1 ctor
      // NO A ctor
   End B1 ctor
   Start B2 ctor
      // NO A ctor
   End B2 ctor
End C ctor
Run Code Online (Sandbox Code Playgroud)

重要的逻辑区别是类型 A 的虚拟继承的基类子对象是最派生类的一部分并受它的控制(此处C)。

B 的构造函数一无所知也无法访问A. 因此,它们不能构造 的子对象A,包括基类。