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

时间:2014-09-15 16:02:55

标签: c++ inheritance virtual-inheritance

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

#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构造函数。

但我读到:&#34;来自最衍生的。&#34; 资料来源:c++ virtual inheritance

这里得到的最多的是D然后是B和C,然后只有A.那么为什么在没有考虑参数B,D,C从它们的构造函数转移到它的情况下首先调用A? 感谢。

代码:

#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

5 个答案:

答案 0 :(得分:5)

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

  1. 您调用派生程度最高的ctor(通过初始化变量; 自动存储类static,动态,等等。
  2. ctor初始化所有子对象:
    • 委托ctor调用另一个派生最多的人。
    • 非委派代理人自己完成工作:
      1. Iff是左派第一个声明顺序中派生最多的ctor,virtual。 (这意味着忽略了由非最多派生的ctors给出的虚拟基础ctor的参数)
      2. 左前一个声明顺序中的其他直接基础。
      3. 声明顺序中的成员。
  3. ctors身体跑。
  4. 由于你的最基础的ctor是唯一的虚拟基础,它的身体是第一个运行的ctor-body,由最派生的ctor直接调用。

    通常,调用virtual函数,typeiddynamic_cast是安全的,但不是在基础子对象全部初始化之前:May I call a virtual function to initialize a base-class sub-object?

答案 1 :(得分:4)

这意味着最大派生类的责任是初始化任何虚拟基础子对象,以及它立即派生的子对象。也就是说,所有基础构造函数都是从派生最多的构造函数中调用的: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

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

答案 2 :(得分:1)

如果构造函数代码在构造基类之前运行,那么如何建议任何派生类的行为?虽然技术上可行,但它完全没用。

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

答案 3 :(得分:1)

你说

  

我读到在虚拟继承中,构造函数被称为&#34;来自最衍生的&#34;。

这是事实。让我详细说明一下。

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

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

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

如果您的子类型为D

struct E : public D
{
};

并创建E的实例,然后将A的构造函数调用E的构造函数。

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

  

12.6.2初始化基础和成员

     

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

     

- 首先,仅对于如下所述的派生类最多的构造函数,虚拟基类应按它们出现在基类有向无环图的深度优先从左到右遍历的顺序进行初始化。 ,其中“从左到右”是派生类 base-specifier-list 中基类名称的出现顺序。

答案 4 :(得分:0)

这就是对象的构建方式。

更新

检查这个例子:

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构造函数的控件将被转移到它的基类&#39;构造函数,在C构造函数可以执行之前。这首先是我对基类的意思。

更新:

通过绘制对象关系可以最好地回答您的问题 我们在顶部有A,在底部有D

  

&#34;在虚拟继承中,[virtual base]构造函数是从派生最多的&#34; [type&#39; s构造函数]。 &#34;

通过上述声明,他们要求您从最派生类型的构造函数(D)开始,并横向到最基类的&#39; (A)构造函数。

更新:

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

  

构造函数本身可以重定向&#34;到基地的构造函数。返回后,它继续使用自己的构造函数。你想念的是用花括号写的东西不是整个实现。它只是在调用base ctor并初始化成员变量之后发生的事情。与ctor相同:在调用成员变量的ctor然后调用基数的ctor之前,用大括号写下要执行的内容。