【问题标题】:Why must virtual base classes be constructed by the most derived class?为什么虚拟基类必须由最派生的类来构造?
【发布时间】:2017-11-03 14:36:21
【问题描述】:

以下代码无法编译:

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

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

// most derived class
class C: public B {
public:
    C() {} // wrong!!!
};

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

// most derived class
class C: public B {
public:
    C(): A(0) {} // OK!!!
};

确实有效。

显然,原因是虚拟基类必须始终由大多数派生类构造。

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

【问题讨论】:

标签: c++ inheritance virtual-inheritance


【解决方案1】:

因为它避免了这种情况:

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

【讨论】:

  • 著名的钻石问题
  • 这不是多重继承唯一可能的歧义,编译器同样可以很好地检测到它。所以这个论点没有说服力。
  • @PeterA.Schneider 如果构造函数之一是在不同的翻译单元中定义的,则编译器无法检测到它。检测也不等于固定。如果您无法修改 B0B1,您将如何解决此示例中的歧义?
  • @PeterA.Schneider 假设您不需要 C 来定义它在某些情况下如何构造 A,那么您在哪里画线?当只有一个基地?仅当 B0 和 B1 都使用 constexpr 值时,它们是相同的值吗?仅当 B0 和 B1 使用相同的文字时?
  • @Oktalist True on both accounts...我发现这个问题类似于不明确的父类函数调用。但是函数是类声明的一部分,而ctors的实现不是,所以对:编译器通常看不到它们。确实没有语言结构可以选择其中一个初始化而不是另一个初始化(与通过范围解析消除歧义的函数调用相反)。
【解决方案2】:

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

例如:

class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2 // A is shared, and would have one copy only.

【讨论】:

  • 共享基类对象(此处为 A 类型)实际上必须最后创建(在最派生对象之前,即在其其他 B1 和 B2 类型的基类对象之后)。跨度>
【解决方案3】:

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

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

class A;
class B1 : A;
class B2 : A;
class C: B1,B2

在这种情况下,为每个 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

现在考虑虚拟继承

class A;
class B1 : virtual A;
class B2 : virtual A;
class C: B1,B2 

一个事件的顺序是

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

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

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

【讨论】:

  • 这里,A不是从B1或B2调用的,而是从C调用的,但是正如你提到的,如果A是B1和B2之后的构造函数,并且B1和B2的构造函数使用A的任何变量,会是什么情况?我认为 A 必须首先构建,然后是其余的。请澄清
  • @infiniteloop 听起来很合理。这确实意味着必须在任何派生成员之前构建虚拟基础。
猜你喜欢
  • 1970-01-01
  • 2017-02-10
  • 1970-01-01
  • 2020-11-15
  • 2012-12-19
  • 1970-01-01
  • 2019-11-04
  • 2017-12-18
  • 2018-06-28
相关资源
最近更新 更多