提问者:小点点

双亲类的构造函数序列


我对两个版本的代码有一个问题。 唯一的不同是在parents类之间切换virtual关键字。 发生这种事有什么原因吗?

版本一:

#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 : 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); 
} 

version:Person::Person(int)调用
Person::Person(int)调用
Facturn::Facturn(int)调用
Student::Student(int)调用
Ta::Ta(int)调用

版本二:

#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 :  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)调用
Facturn::Facturn(int)调用
Person::Person(int)调用
Student::Student(int)调用
Ta::Ta(int)调用


共3个答案

匿名用户

初始化类的顺序基于它们在类的基类说明符列表中声明的顺序:

  1. 如果这是派生最多的类,则初始化虚拟基类。 它们的初始化顺序基于对该类的基类说明符列表进行深度优先从左到右的搜索。
  2. 从左到右初始化非虚拟基本类
  3. 此类的成员基于声明顺序构造
  4. 此类的构造函数主体运行

这个过程对每个初始化的对象递归地重复。

对于第一个示例:

  • ta是派生最多的类,因此首先初始化它的person虚拟基对象。
    • person的构造函数主体运行并打印其消息。
    • Factory具有一个非虚拟基类Percers,因此它初始化自己的Percers子对象。
      • person的构造函数主体运行并打印其消息
      • Student不是派生最多的类,因此它没有获得自己的Person子对象来初始化。
      • student的构造函数主体运行并打印其消息

      结果是构造函数的主体按以下顺序执行:

      1. 教员
      2. 学生
      3. ta

      对于第二个示例:

      • ta是派生最多的类,因此首先初始化它的person虚拟基对象。
        • person的构造函数主体运行并打印其消息。
        • Factor不是派生最多的类,因此它没有获得自己的Person子对象来初始化。
        • factury的构造函数主体运行并打印其消息
        • student有一个非虚拟基类perers,因此它初始化自己的perers子对象。
          • person的构造函数主体运行并打印其消息

          结果是构造函数的主体按以下顺序执行:

          1. 教员
          2. 学生
          3. ta

          注意,在这两种情况下,都有两个person子对象。 对于教员学生共享单个人员子对象,两者都必须从人员虚拟继承,即:

          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;
              }
          };
          

          在这种情况下,逻辑是:

          • ta是派生最多的类,因此首先初始化它的person虚拟基对象。
            • person的构造函数主体运行并打印其消息。
            • Factor不是派生最多的类,因此它没有获得自己的Person子对象来初始化。
            • factury的构造函数主体运行并打印其消息
            • Student不是派生最多的类,因此它没有获得自己的Person子对象来初始化。
            • student的构造函数主体运行并打印其消息

            从而导致类的构造函数主体按以下顺序执行:

            1. 教员
            2. 学生
            3. ta

匿名用户

初始化的顺序根据继承的基本类从左到右设置。 它基本上忽略了您在构造函数列表中设置的顺序。

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

将为第一种情况初始化为:

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

但是虚拟基础类是先初始化的,所以我们有:

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

现在,当factury(x)初始化时,也会初始化person(x)(因为它是从它派生的)。 然后初始化student,最后初始化ta-对象本身。

您可以通过使用-wall编译来看到初始值设定项列表中的不正确之处。

匿名用户

首先,要以正确的方式实现“钻石”多重继承,你的类教师和学生都必须是虚拟的人的继承。 在本例中,您只会在输出中看到一个对Person构造函数的调用。 这就是目的:只调用祖父母的构造函数一次。

您可能使用非常非限制性的编译器,因为对代码的限制性编译(例如使用VS2017)会立即报告一个错误:error C2385:ambiguous access of'person'。 而这正是预期的行为。

附加说明:使用虚拟继承会推迟对继承类构造函数的调用,以便由它的“孙子”执行。 在您的例子中,类TA是Person的孙子,只有它调用Person的构造函数,而教师和学生不调用。