在虚拟继承的情况下如何处理祖父母类的成员?

时间:2016-07-05 19:27:19

标签: c++ inheritance constructor

我在某处阅读了以下代码,作为在多重继承的情况下解决钻石问题的示例:

#include<iostream>
using namespace std;

class A
{
int x;

public:
    A() {}
    A(int i) { x = i; }
    void print() { cout << x; }
};

class B: virtual public A
{
public:
    B():A(10) { }
};

class C: virtual public A 
{
public:
    C():A(100) { }
};

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

假设类D定义如下:

class D: public B, public C 
    {
    public:
        D():B(),C(){}
    };

我在打印中得到一些垃圾值。如果类D定义如下(显式调用A的参数化构造函数):

class D: public B, public C 
    {
    public:
        D():B(),C(),A(20){}
    };

我得到20作为输出。在第一种情况下,我可以理解调用默认的A()构造函数,因此作为x的垃圾值未设置为任何值。

然而,在第二种情况下,其不清楚。何时调用A(int)的参数化构造函数?如果我理解正确,呼叫顺序取决于继承的顺序。由于B首先被继承,B的构造函数调用优先于C。由于B继承AA()将首先被称为。然后将调用B的构造函数。然后将调用C的构造函数。最后,将调用A(int),因为A的构造函数在类D中显式调用。如果是这种情况,则输出对于第二种情况是合理的。但是,这与下面的代码的输出相矛盾:

#include<iostream>
using namespace std;
class Person {
public:
    Person(int x) { cout << "Person::Person(int ) called" << endl; }
    Person()     { cout << "Person::Person() called" << endl; }
};

class Faculty : virtual public Person {
public:
    Faculty(int x):Person(x) {
    cout<<"Faculty::Faculty(int ) called"<< endl;
    }
};

class Student : virtual public Person {
public:
    Student(int x):Person(x) {
        cout<<"Student::Student(int ) called"<< endl;
    }
};

class TA : public Faculty, public Student {
public:
    TA(int x):Student(x), Faculty(x), Person(x) {
        cout<<"TA::TA(int ) called"<< endl;
    }
};

int main() {
    TA ta1(30);
}

该计划的输出:

Person::Person(int ) called
Faculty::Faculty(int ) called
Student::Student(int ) called
TA::TA(int ) called

为什么在这种情况下Person(int)在开头调用,而不是在最后调用?

4 个答案:

答案 0 :(得分:2)

N4594 12.6.2 / 13:

  

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

     
      
  • 首先,并且仅针对派生程度最高的类(1.8)的构造函数,虚拟基类在   它们出现在基类的有向无环图的深度优先从左到右遍历中的顺序,   其中“从左到右”是派生类base-specifier-list中基类出现的顺序。
  •   
  • 然后,直接基类按声明顺序初始化,因为它们出现在base-specifier-list中   (无论mem-initializers的顺序如何)。
  •   
  • 然后,按照在类定义中声明的顺序初始化非静态数据成员   (再次无论mem-initializers的顺序如何)。
  •   
  • 最后,执行构造函数体的复合语句。
  •   
     

[注意:声明命令的目的是确保基础和成员子对象在销毁中被销毁   初始化的逆序。 - 后注]

答案 1 :(得分:1)

构造始终从基础class开始。如果有多个基础class es,那么它从最左边的基数开始。 (旁注:如果存在virtual继承,则给予更高的优先级。然后它转向成员字段。它们按声明的顺序初始化。最后构建了class本身。

析构函数的顺序完全相反

答案 2 :(得分:0)

使用虚拟继承,只在最派生的类中调用虚拟类的构造函数。

初始化顺序不依赖于初始化列表的顺序,而是依赖于类中声明的顺序。

答案 3 :(得分:0)

  

由于B继承A,所以首先会调用A()。那么B&#39   构造函数将被调用。

A被虚拟遗传时,情况并非如此。

当一个类被虚拟继承时,它最有效地由派生类继承,以便调用构造函数和析构函数。那是什么之间的虚拟继承。

由于D派生自BC,因此在调用构造函数和析构函数时,类层次结构D会继承A,因为D 1}}是派生最多的类。