【问题标题】:How members of grandparent class are treated in case of virtual inheritance?在虚拟继承的情况下如何处理祖父母类的成员?
【发布时间】:2016-07-05 19:27:19
【问题描述】:

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

#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继承A,A()将首先被调用。然后将调用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) 在开头调用,而不是在最后?

【问题讨论】:

    标签: c++ inheritance constructor


    【解决方案1】:

    N4594 12.6.2/13:

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

    • 首先,并且只针对最派生类(1.8)的构造函数,虚基类在初始化 它们出现在基类的有向无环图的深度优先从左到右遍历中的顺序, 其中“从左到右”是派生类 base-specifier-list 中基类的出现顺序。
    • 然后,直接基类按照它们出现在基说明符列表中的声明顺序进行初始化 (不管 mem-initializers 的顺序如何)。
    • 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化 (同样不管 mem-initializers 的顺序如何)。
    • 最后,构造器主体的复合语句被执行。

    [ 注意:声明顺序必须确保基子对象和成员子对象在 初始化的相反顺序。 ——尾注]

    【讨论】:

      【解决方案2】:

      构造总是从基础class 开始。如果有多个基数classes,则从最左边的基数开始。 (旁注:如果有 virtual 继承,则优先级更高)。然后轮到成员字段。它们按照声明的顺序进行初始化。最后构造了class 本身。

      析构函数的顺序正好相反

      【讨论】:

        【解决方案3】:

        因为 B 继承了 A,所以首先调用 A()。然后B 构造函数将被调用。

        当A 被虚拟继承时,这并不完全正确。

        当一个类被虚拟继承时,它被最派生的类有效地继承,目的是调用构造函数和析构函数。这就是虚拟继承之间的关系。

        由于D 派生自B 和C,在该类层次结构中D 在调用构造函数和析构函数时继承A,因为D 是派生最多的类。

        【讨论】:

          【解决方案4】:

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

          并且初始化的顺序不依赖于初始化列表的顺序,而是依赖于类内部的声明顺序。

          【讨论】:

          • 这个答案解释了 where 应该调用虚拟基类的构造函数。 OP询问何时调用了虚拟基类的构造函数。
          • @PcAF:我可能不清楚,但这是我第二段的目标。
          • 好吧,我认为类内(基类)声明的顺序是不清楚的术语。
          猜你喜欢
          • 2015-11-25
          • 2012-05-19
          • 1970-01-01
          • 2017-12-12
          • 1970-01-01
          • 2012-09-27
          • 2012-05-03
          • 2013-06-21
          • 1970-01-01
          相关资源
          最近更新 更多