关于构造函数调用和虚拟基类的顺序的困惑

时间:2018-10-09 13:33:51

标签: c++ c++11

通话单.cpp

#include <iostream>

class A
{
public:
    A()
    {
        std::cout << "A" ;
    }

};

class B: public A
{
public:
    B()
    {
        std::cout << "B" ;
    }
};

class C: virtual public A
{
public:
    C()
    {
        std::cout << "C" ;
    }

};

class D: public B, public C
{
public:
    D()
    {
        std::cout << "D" ;        
    }

};


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

编译

g++ order-of-call.cpp -std=c++11

输出

AABCD

为什么两个A在一起输出?我期待的是类似ABACD的东西。但是如果我这样改变继承顺序 class D: public C, public B,输出如预期的ACABD。是订单的一部分,还是g ++的专有内容。

4 个答案:

答案 0 :(得分:6)

这是有道理的,因为虚拟基类是在非虚拟基类之前构造的。因此,根据您的情况,它是:virtual Anon-virtual ABCD。如果更改继承顺序,则顺序为virtual ACnon-virtual ABD。检出此内容:https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.cbclx01/cplr389.htm

初始化类的顺序如下:

  
      
  1. 虚拟基类的构造函数按照它们在基列表中出现的顺序执行。
  2.   
  3. 非虚拟基类的构造函数按声明顺序执行。
  4.   
  5. 类成员的构造函数以声明顺序执行(无论其在初始化列表中的顺序如何)。
  6.   
  7. 构造函数的主体被执行。
  8.   

答案 1 :(得分:4)

这似乎很相关:https://isocpp.org/wiki/faq/multiple-inheritance#mi-vi-ctor-order,尤其是:

  

要执行的第一个构造函数是层次结构中任何位置的虚拟基类。

     

所有虚拟基类构造函数完成后,构造顺序通常是从基类到派生类。因此,如果类D继承自B1和B2的乘法,则首先执行B1的构造函数,然后执行B2的构造函数,然后执行D的构造函数。

     

请注意,顺序B1和B2(或B1a然后B1b)由基类在类的声明中出现的顺序确定。

因此,您看到的第一个A总是通过C->A的虚拟继承。休息通常是从左到右深入。

答案 2 :(得分:1)

构建顺序首先是虚拟基础,然后按从左到右的顺序(递归)深入构建非虚拟基础。

因此,在构造D时,将首先构造类A的虚拟基数C(因此将输出第一个'A')。然后开始在B中构建D,这首先构造了非虚拟A(输出第二个'A'),它是{{1 }},然后调用B的构造函数(输出B)。然后调用'B'的构造函数-它的基础C是虚拟的,因此不再构造,然后调用A的构造函数(输出C)。最后,调用'C'的构造函数(输出'D')。

答案 3 :(得分:-1)

如果您在此处添加缺少的虚拟:

class B: virtual public A

这将解决多重继承问题,并且每个类将获得一个A实例

然后输出将是简单的ABCD或更改订单的情况

class D: public C, public B

输出:

ACBD

符合标准,此行为独立于您使用的编译器

但是,如果确实需要,则仅对于C类是虚拟的。在任何情况下都将首先调用虚拟承包商,然后根据标准再次调用它:

ISO / IEC JTC1 SC22 WG21 N 3690

  

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

     
      
  • 首先,仅适用于大多数   派生类(1.8),虚拟基类按以下顺序初始化   它们出现在定向的深度优先的从左到右的遍历中   基类的非循环图,其中“从左到右”是   基类在派生类中的外观   base-specifier-list。

  •   
  • 然后,直接基类在   它们出现在基本说明符列表中的声明顺序   (与mem-initializers的顺序无关)。

  •   
  • 然后,非静态数据成员将按照它们在   类定义(同样,无论顺序如何   mem-initializers)。
  •   
  • 最后,   构造函数体被执行。
  •