【问题标题】:sequence of the constructor of two parent classes两个父类的构造函数顺序
【发布时间】:2020-10-14 20:01:55
【问题描述】:

我对两个版本的代码有一个问题。 唯一不同的是在父类之间切换 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); 
} 

版本的输出: Person::Person(int) 被调用
Person::Person(int) 被调用
Faculty::Faculty(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) 被调用
Faculty::Faculty(int) 调用
Person::Person(int) 被调用
Student::Student(int) 调用
TA::TA(int) 调用

【问题讨论】:

  • 附带问题:Faculty 实体与Person 实体有关系“IS A”吗?
  • 首先初始化虚拟基类。所以,是的,添加 virtual 关键字确实会影响初始化顺序。从Person 继承一次有虚拟继承,一次没有仍然意味着有两个不同的Person 类型的基,但每次都会首先初始化虚拟的。

标签: c++ inheritance multiple-inheritance virtual-inheritance


【解决方案1】:

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

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

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


第一个例子:

  1. TA 是派生最多的类,所以它的Person 虚拟基对象首先被初始化。
    • Person 的构造函数主体运行并打印其消息。
  2. Faculty 是第一个声明的基类,因此接下来会对其进行初始化。
    • Faculty 有一个非虚基类Person,所以它初始化了自己的Person 子对象。
      • Person 的构造函数主体运行并打印其消息
    • Faculty 的构造函数主体运行并打印其消息
  3. Student 是下一个基类,因此接下来会对其进行初始化。
    • Student 不是派生最多的类,因此它没有自己的 Person 子对象来初始化。
    • Student 的构造函数主体运行并打印其消息
  4. TA 的构造函数主体运行并打印其消息

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

  1. Person
  2. Person
  3. Faculty
  4. Student
  5. TA

第二个例子:

  1. TA 是派生最多的类,所以它的Person 虚拟基对象首先被初始化。
    • Person 的构造函数主体运行并打印其消息。
  2. Faculty 是第一个声明的基类,因此接下来会对其进行初始化。
    • Faculty 不是派生最多的类,因此它没有自己的 Person 子对象来初始化。
    • Faculty 的构造函数主体运行并打印其消息
  3. Student 是下一个基类,因此接下来会对其进行初始化。
    • Student 有一个非虚基类Person,所以它初始化了自己的Person 子对象。
      • Person 的构造函数主体运行并打印其消息
    • Student 的构造函数主体运行并打印其消息
  4. TA 的构造函数主体运行并打印其消息

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

  1. Person
  2. Faculty
  3. Person
  4. Student
  5. TA

请注意,在这两种情况下,都有 两个 Person 子对象。对于FacultyStudent 共享一个Person 子对象,两者都必须从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;
    }
};

在这种情况下,逻辑是:

  1. TA 是派生最多的类,所以它的Person 虚拟基对象首先被初始化。
    • Person 的构造函数主体运行并打印其消息。
  2. Faculty 是第一个声明的基类,因此接下来会对其进行初始化。
    • Faculty 不是派生最多的类,因此它没有自己的 Person 子对象来初始化。
    • Faculty 的构造函数主体运行并打印其消息
  3. Student 是下一个基类,因此接下来会对其进行初始化。
    • Student 不是派生最多的类,因此它没有自己的 Person 子对象来初始化。
    • Student 的构造函数主体运行并打印其消息
  4. TA 的构造函数主体运行并打印其消息

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

  1. Person
  2. Faculty
  3. Student
  4. TA

【讨论】:

    【解决方案2】:

    首先,要正确实现“钻石”多重继承你的Faculty和Student类都必须虚拟继承自Person。 在这种情况下,您只会在输出中看到对 Person 构造函数的一次调用。这就是目的:只调用一次祖父母的构造函数。

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

    附加说明:使用 virtual 继承会推迟对继承类构造函数的调用,由其“孙子”执行。在您的情况下,TA 类是 Person 的孙子,只有它调用 Person 的构造函数,而 Faculty 和 Student 不调用。

    【讨论】:

      【解决方案3】:

      对于继承的基类,初始化的顺序是从左到右设置的。它基本上会忽略您在构造函数列表中设置的顺序。

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

      现在,当Faculty(x) 被初始化时,Person(x) 也被初始化(因为它派生自它)。然后 Student 初始化它,最后是 TA-object 本身。

      使用-Wall编译可以看到初始化列表中的错误。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-20
        • 1970-01-01
        • 2018-02-28
        • 2011-01-21
        • 1970-01-01
        • 2014-01-28
        • 2012-04-10
        • 1970-01-01
        相关资源
        最近更新 更多