首页 > 解决方案 > 使用非默认虚拟基构造函数的菱形继承图中的“中间类”:为什么不是编译错误?

问题描述

考虑一个菱形继承图(即虚拟基类)。我们从前面的 问题中知道 ,在构造时,派生最多的类直接调用(虚拟)基的默认(0-arg)构造函数。

但是我们也从上一个问题的答案中知道(例如,在这里 ,如果菱形中的“中间”类具有由最派生类使用的构造函数,并且这些构造函数“调用”它们的(虚拟)的非默认构造函数基类(通过初始化列表)然后被尊重......尽管“中间”类的构造函数的主体执行。

这是为什么?我原以为这应该是一个编译错误。(当然,在声明最派生类并创建菱形时检测到。)

我正在寻找两件事:

我正在谈论的代码示例遵循下面的实际和预期输出:

B 0arg-ctor
Mf 0arg-ctor
Mt 0arg-ctor
useD

预期输出:

ERROR: (line 19) struct `D` creates a diamond inheritance graph where an explicitly
    written invocation of a virtual base class constructor is ignored (for base 
    classes `Mf`and `Mt` and ancestor virtual base class `B`

代码:

#include <iostream>
using namespace std;

struct B {
    B() noexcept { cout << "B 0arg-ctor" << endl; };
    B(bool) noexcept { cout << "B 1arg-ctor" << endl; };
};

struct Mf : public virtual B
{
    Mf() : B(false) { cout << "Mf 0arg-ctor" << endl; }
};

struct Mt : public virtual B
{
    Mt() : B(true) { cout << "Mt 0arg-ctor" << endl; }
};

struct D : public Mf, public Mt { };

void useD(D) { cout << "useD" << endl; }

int main()
{
    D d;
    useD(d);
    return 0;
}

标签: c++constructorlanguage-lawyervirtual-inheritanceinitialization-list

解决方案


初始化基和成员的规则在[class.base.init]中指定。

特别是在p7

在执行不是最派生类的任何类的构造函数期间,忽略 mem-initializer-id 表示虚拟基类mem-initializer

及其在p13中的补充:

首先,并且仅对于最派生类([intro.object])的构造函数,虚拟基类按照它们在基类的有向无环图的深度优先从左到右遍历中出现的顺序进行初始化,其中“从左到右”是派生类base-specifier-list中基类的出现顺序。

因此初始化器B(true)和在初始化B(false)时被忽略MfMt因为它们不是最派生的类,并且初始化D B. 没有提供它的初始化器,所以B()使用它。


使这个无法编译基本上是不可能的?首先,考虑:

struct Mf : public virtual B { };
struct D : public Mf { };

初始化B,但隐含。您是否希望这是一个错误,Mf因为它的初始化将被忽略?我认为不会 - 否则这种语言功能将完全无法使用。现在,怎么样:

struct Mf : public virtual B { Mf() : B() { } };
struct D : public Mf { };

这是一个错误吗?它基本上意味着同样的事情。如果Mf有需要初始化的成员,而我按照习惯,就像列出基类一样怎么办?

struct Mf : public virtual B { Mf() : B(), i(42) { } int i; };
struct D : public Mf { };

好吧,你说,如果你真的提供论据,你只会出错。这是一个不同的误解出现的地方:

我们从前面的问题中知道,在构造时,派生最多的类直接调用(虚拟)基的默认(0-arg)构造函数。

这不是真的(也不是那些答案所说的)。最派生的类初始化虚基——但这种初始化不必是默认的。我们可以这样写:

struct D : public Mf, public Mt { D() : B(true) { } };

B()实际上,和之间没有有趣的区别B(true)。想象一下构造函数只是B(bool = true),那么用户是否提供参数是否重要true?如果一个是错误而另一个不是,那会很奇怪,对吧?

如果你继续沿着这个兔子洞走下去,我想你会发现把它变成一个错误要么会非常狭窄,要么会非常严格。


推荐阅读