使用具有虚拟多重继承的基构造函数

Using base constructors with virtual multiple inheritance

本文关键字:构造函数 多重继承 虚拟      更新时间:2023-10-16

运行下面的代码,我希望得到以下控制台输出:

B int v

D

取而代之的是,E 的构造函数调用 B 的默认构造函数,结果是:

B

D

实现正确构造的一种方法是在 E 中重新声明相同的 D 构造函数(即注释的代码部分),但我仍然希望有比这更好的解决方案。

准备运行代码,带有 -std=c++11 标志:

#include <iostream>
class A {
public:
virtual void fun() = 0;
virtual void fun2();
void fun3();
};
class B : public A {
public:
B();
B(int);
void fun();
void fun2();
};
class C : virtual public B {
public:
using B::B;
void fun();
};
class D : virtual public B {
public:
D();
D(int);
void fun();
};
class E : public C, public D {
public:
using D::D;
void fun();
};
void A::fun2() {}
void A::fun3() {}
B::B() { std::cout << "Bn"; }
B::B(int v1) { std::cout << "B int vn"; }
void B::fun() {}
void B::fun2() {}
void C::fun() {}
D::D() { std::cout << "Dn"; }
D::D(int v1) : B(v1) { std::cout << "Dn"; }
void D::fun() {}
/*E::E(int v1): D::B(v1){  std::cout <<"En";}  */ void E::fun() {}
int main() {
E Eob(1);
return 0;
}

结论: 最终,定义 E 的显式构造函数,并显式调用虚拟基类 B(请参阅注释的代码段),是必要的。

正如Eljay首先正确评论的那样,我假设"使用D::D"是错误的使用。 "using"关键字永远不会重新定义 E 的构造函数,这与 D 的构造函数类似;它只是调用基类 D 的构造函数,并强制基类 D 构造。后一个事实触发了虚拟基类构造的层次结构(正如 StoryTeller 在下面回答的那样),并导致我的问题根据需要构造一个类 E 的对象。

这是一个相当常见的陷阱。首先让我说,A的存在是一条红鲱鱼。您可以通过完全省略示例来缩短示例。

您看不到使用B(int)的原因是由于C++标准中的两个子句。第一类 [inhctor]/8 说:

隐式定义的继承构造函数执行一组 将由用户编写的类执行的类的初始化 该类的内联构造函数,具有 mem 初始值设定项列表,其 只有 mem 初始值设定项具有命名基数的 mem-initializer-id 在 using-声明的嵌套名称说明符中表示的类 以及下面指定的表达式列表,其中 其函数体中的复合语句为空 ([class.base.init])。

这说明 c'tor 继承自D年的E被翻译成这样的东西:

E::E(int i) : D(i) {}

不幸的是,这是你的问题。因为在咨询 [class.base.init/10] 时:

非委托构造函数中,初始化在 以下顺序:

  • 首先,并且仅对于派生最多的类([intro.object]) 的构造函数,虚基类按以下顺序初始化 它们出现在定向的深度优先从左到右遍历上 基类的非循环图,其中"从左到右"是 派生类中基类的外观 基本说明符列表。

我们看到(强调我的)只有最派生的 c'tor 才能并且将初始化虚拟基础。最衍生的c'tor是如何做到这一点的?正如我们之前所写。它从其成员初始值设定项列表中省略虚拟基。因此,虚拟基础是默认初始化的

如果你想把一个整数传递给B的c'tor。你需要自己定义E的构造函数:

E::E(int i) : B(i), D(i) {}