c++中的虚拟继承被误解了

virtual inheritance in c++ misunderstood

本文关键字:误解 继承 虚拟 c++      更新时间:2023-10-16

我读到在虚拟继承中,构造函数是"从最派生的"调用的。请考虑以下代码。在我看来,这里最派生的类是D,然后是B和C,"最不派生的"是A。那么,为什么最"基本"的构造函数首先被调用,而不是"最派生"的构造函数呢?谢谢

#include <iostream>
using namespace std;
struct A
{
    A()
    {
        cout << "A default constructor" << endl;
    }
};
struct B : virtual public A
{
    B() : A()
    {
        cout << "B default constructor" << endl;
    }
};
struct C : virtual public A
{
    C() : A()
    {
        cout << "C default constructor" << endl;
    }
};
struct D : public B, public C
{
    D() : B(), C()
    {
        cout << "D default constructor" << endl;
    }
};
int main()
{
    D d;
}

这是输出:

A default constructor
B default constructor
C default constructor
D default constructor

更新:


好的。因此,请考虑以下代码。注意,虽然D、B和C构造函数发送了7,但还是打印了10。实际上,这里的基类IS是被调用的第一个基类。没有从D到B再到A的链。首先调用了A()(实际上它是默认的构造函数)。然后只调用了B和C构造函数。

但我读到:"从最派生的。"来源:c++虚拟继承

这里导出最多的是D,然后是B,C,然后是A。那么,为什么首先调用A,而不考虑参数B,D,C从它们的构造函数传递给它呢?谢谢

代码:

#include <iostream>
using namespace std;
struct A
{
    int _x;
    A()
    {
        _x = 10;
        cout << "A default constructor" << endl;
    }
    A(int x)
    {
        _x = x;
        cout << "A NOT-default constructor" << endl;
    }
};
struct B : virtual public A
{
    B(int x=7) : A(x)
    {
        cout << "B  constructor" << endl;
    }
};
struct C : virtual public A
{
    C(int x=7) : A(x)
    {
        cout << "C  constructor" << endl;
    }
};
struct D : public B, public C
{
    D(int x=7) : B(x), C(x)
    {
        cout << "D  constructor" << endl;
    }
};
int main()
{
    D d;
    cout << d._x;
}

输出:

A default constructor
B  constructor
C  constructor
D  constructor
10

在C++中构造顺序非常简单:

  1. 您调用最派生的ctor(通过初始化变量;自动存储类static、dynamic等等)
  2. 该ctor初始化所有子对象:
    • 委托的ctor调用另一个派生度最高的ctor
    • 非委托人自己做工作:
      1. 如果是派生次数最多的ctor,则virtual以左第一个声明顺序为基础。(这意味着忽略由非大多数派生的actor给出的虚拟基actor的参数)
      2. 其他直接基按左第一个声明顺序排列
      3. 成员按申报顺序排列
  3. ctors主体运行

由于最基本的ctor是唯一的虚拟基,因此它的主体是第一个运行的ctor主体,由最派生的ctor直接调用

通常,调用virtual函数、typeiddynamic_cast是安全的,尽管不是在基本子对象都初始化之前:我可以调用虚拟函数来初始化基类子对象吗?

这意味着最派生的类有责任初始化任何虚拟基本子对象,以及它立即派生的那些对象。也就是说,所有的基本构造函数都是从最派生的构造函数调用的:D的构造函数调用A的构造函数,然后调用BC的构造函数,最后初始化自己。这对于确保共享基对象只初始化一次是必要的,并且在从它派生的任何类之前初始化

这并不意味着顺序是从最多派生到最少派生的。与常规继承一样,基本子对象总是首先初始化,因此在初始化派生类时它们是可用的。

为了回答您更新的问题,由于D初始化A,它将调用默认构造函数,除非其初始化程序列表包含A:的条目

D(int x=7) : B(x), C(x)        // calls A(), initialising with 10
D(int x=7) : A(x), B(x), C(x)  // calls A(int), initialising with 7

B(或C)的初始化程序列表中的A的任何条目仅在B(或C)是派生最多的类时使用(因此负责初始化A)。

如果任何派生类的构造函数代码在构造基类之前运行了,您会如何建议它的行为?虽然从技术上讲是可能的,但它完全没有用。

你观察到的行为是唯一理智的行为,与虚拟继承无关。

你说

我读到在虚拟继承中,构造函数被称为";从最派生的";。

这是真的。让我详细说明。

在您的情况下,D是派生最多的。

当您构造D的实例时,A的构造函数是从D的构造函数调用的,因为D的每个实例只有A的实例。A的构造函数将不会从BC的构造函数中调用。

构造B的实例时,A的构造函数将从B的构造函数中调用。类似地,对于CCD_ 31的实例。

如果您有D的子类型,

struct E : public D
{
};

并且您创建了E的实例,那么A的构造函数将从E的构造函数中调用。

C++标准草案(N3337)提到了涉及虚拟基类的初始化:

12.6.2初始化基地和成员

5初始化应按以下顺序进行:

--首先,并且仅对于如下所述的最派生类的构造函数,虚拟基类应按照它们在基类的有向无环图的深度优先左到右遍历中的出现顺序进行初始化,其中"从左到右"是派生类基说明符列表中基类名称的出现顺序。

这就是构建对象的方式。

更新

看看这个例子:

class A{
    A()
    {
    cout << "Constructor A"<<endl;
    }
    ~A()
    {
    cout << "Destructor A"<<endl;
    }
}
class B : public A{
    B()
    {
    cout << "Constructor B"<<endl;
    }
    ~B()
    {
    cout << "Destructor B"<<endl;
    }
}
class C : public B{
    C()
    {
    cout << "Constructor C"<<endl;
    }
    ~C()
    {
    cout << "Destructor C"<<endl;
    }
}

创建类C:的对象

C obj;

输出如下:

构造函数A
构造函数B
构造函数C
析构函数C
析构函数B
析构器A

执行的原因是:

当一个类从另一个类派生时,它派生该类的属性。派生类的函数可能取决于基类的函数,也可能不取决于基类,但决不能相反。假设派生类依赖于基类功能,那么在初始化派生类之前,正确初始化基类是很重要的。

更新:

当创建C的对象时,在C的构造函数可以执行之前,C构造函数的控制权将转移到它的基类构造函数。这就是我所说的基类优先的意思。

更新:

最好通过绘制对象关系来回答您的问题
A在顶部,D在底部。

"在虚拟继承中,[virtual-base]构造函数是从派生最多的"[type的构造函数]调用的。"

通过上面的语句,他们要求您从最派生类型的构造函数(D)开始,并横向到最基类的构造函数(A)。

更新:

@leemes在评论中使执行流程更加清晰:

是构造函数本身"重定向"到基的构造函数。返回后,它继续使用自己的构造函数。您忽略的是,用大括号编写的内容并不是整个实现。它只是在调用基ctor并初始化成员变量之后出现的内容。与ctor相同:在调用成员变量的ctor,然后调用基的ctor之前,用大括号编写要执行的内容。