C++ 虚拟继承和构造函数

C++ Virtual inheritance and constructors

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

我有以下c ++代码(如果这很重要,VS2013(:

#include <iostream>
using namespace std;
struct  A{
A() { cout << "A()" << endl; }
A(int i) { cout << "A(" << i << ")" << endl; }
};
struct B : /*virtual*/ public A{
B(int i) : A(i){ cout << "B(" << i << ")" << endl; }
};
struct C : /*virtual*/ public A{
C(int i) : A(i){ cout << "C(" << i << ")" << endl; }
};
struct D : /*virtual*/ public B, /*virtual*/ public C{
D() : B(2), C(3){ cout << "D()" << endl; }
};
void main() {
D d;
system("pause");
}

该代码实现了菱形继承,总共有 4 个继承: B:A, C:A, D:B, D:C

由于这些继承都没有设置为virtual,我得到以下输出:

A(2( B(2( A(3( C(3( D((

这是有道理的。D(

( 首先调用 B(int x(,它首先调用 A(int x(,然后调用 C(int x(,它首先调用 A(int x(,最后 D(( 本身运行。 但就虚拟继承而言,我有 4 个继承,这意味着我总共有 16 个虚拟\非虚拟继承的组合。

弄乱不同的选项会产生一些非常意想不到的结果,我对虚拟继承的了解越多,我似乎就越困惑。

以下是一些示例:

  1. 当仅将 B:A 设置为virtual时,我得到:

A(( B(2( A(3( C(3( D((

这是有道理的 - B 虚拟继承 A,因此,除非另有特别说明,否则 B(int x( 调用 A 的默认构造函数。

  1. 当仅将 C:A 设置为virtual时,我得到:

A(( A(2( B(2( C(3( D((

为什么 A 的构造函数都先于 B 和 C ?这种行为对我来说毫无意义。

  1. 当仅将 D:C 设置为virtual时,我得到:

A(3( C(3( A(2( B(2( D((

为什么 C 的构造函数会先于 B 的构造函数?

我可以继续说下去。 其中一些组合会导致非常意想不到的结果(至少对我来说(。

我知道虚拟继承的基础知识,并在简单的示例中理解它们,并且我看到了很多关于它的问题。但其中一些行为仍然让我感到困惑。

这些多个虚拟继承是否遵循任何特定的规则集?

编辑: 我被重定向到这个问题: 混合基类的虚拟和非虚拟继承 虽然这很有帮助,但我仍然对在任何特定情况下调用 A 的哪个构造函数感到困惑。 我仍然需要一些帮助。

这里有两个大键:

虚拟基类由
  1. 派生最多的类构造函数直接初始化,而不是由其他基类间接初始化。

  2. 虚拟基类
  3. 在任何非虚拟基类之前初始化。

因此,让我们看一下您的示例。

当仅将 B:A 设置为虚拟时,我得到:

A(( B(2( A(3( C(3( D((

这是有道理的 - B 虚拟继承 A,因此,除非另有特别说明,否则 B(int x( 调用 A 的默认构造函数。

B根本不调用A的构造函数。D直接调用AA的虚拟实例。 但是由于D的构造函数没有指定如何初始化A,所以你会得到A的默认构造函数。

B(int i) : A(i)中的A被忽略不是因为A是一个虚拟基,而是因为B不是派生最多的类,因此无法构造其A

当仅将 C:A 设置为虚拟时,我得到:

A(( A(2( B(2( C(3( D((

为什么 A 的构造函数都先于 B 和 C ?这种行为对我来说毫无意义。

这里C::A是唯一的虚拟基类,所以它首先被初始化。 同样,由于D的构造函数没有为A指定初始值设定项,因此该虚拟子对象使用默认构造函数。

然后,非虚拟基类按顺序排列。BC之前,但B首先需要初始化其非虚拟A(使用2(。

当仅将 D:C 设置为虚拟时,我得到:

A(3( C(3( A(2( B(2( D((

为什么 C 的构造函数会先于 B 的构造函数?

唯一的虚拟基类是C,所以它在B之前构造。 但是这次C的构造函数必须首先初始化其非虚拟基子对象C::A。 然后是B,它必须首先初始化其非虚拟子对象B::A

最后,还有"正常"模式,它使A继承都是虚拟的,导致只有一个A子对象,这是虚拟继承的全部点。 将BC虚拟化是没有意义的,除非您希望D以更复杂的方式重用为基类。

#include <iostream>
using namespace std;
struct  A{
A() { cout << "A()" << endl; }
A(int i) { cout << "A(" << i << ")" << endl; }
};
struct B : virtual public A{
B(int i) : A(i){ cout << "B(" << i << ")" << endl; }
};
struct C : virtual public A{
C(int i) : A(i){ cout << "C(" << i << ")" << endl; }
};
struct D : public B, public C{
D() : A(1), B(2), C(3){ cout << "D()" << endl; }
};
void main() {
D d;
system("pause");
}

输出:

A(1( B(2( C(3( D((

注意我在D中添加了一个初始值设定项A(1),以表明虚拟子对象不一定需要使用默认构造函数。 您可以在至少一个A继承是虚拟的任何示例中执行此操作。