基类构造函数是否在派生类构造函数之前实际调用

时间:2014-09-23 14:11:11

标签: c++ constructor derived-class base-class

我知道这个问题有明确的答案:首先调用基类构造函数,然后调用派生类构造函数。

但我并不完全理解"叫做#34;。这是否意味着构造函数的使用开始,或构造函数的使用完成?换句话说,下面的代码有两种可能的顺序:

  1. BaseClass构造函数启动 - > BaseClass构造函数完成 - > DerivedClass构造函数启动 - > DerivedClass构造函数完成。

  2. DerivedClass构造函数启动 - > BaseClass构造函数启动 - > BaseClass构造函数完成 - > DerivedClass构造函数完成。

  3. 哪一个应该是正确的顺序?如果1是正确的,那么编译器在初始化DerivedClass实例之前如何知道调用BaseClass构造函数?

    似乎案例2是正确的:"称为"应该意味着完成构造函数。后续问题是析构函数如何?我知道标准答案是"派生类的析构函数首先被调用"。那么这是正确的顺序:

    • DerivedClass析构函数启动
    • DerivedClass析构函数完成
    • BaseClass析构函数启动
    • BaseClass析构函数完成

    由于

    class BaseClass {
    public:
        BaseClass() {
            cout << "BaseClass constructor." << endl;
        }
    };
    
    class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass() {
            cout << "DerivedClass constructor." << endl;
        }
    };
    
    int main() {
        DerivedClass dc;
    }
    

2 个答案:

答案 0 :(得分:5)

[class.base.init] / 10,强调我的:

  

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

     
      
  • 首先,仅适用于最多的构造函数   派生类,虚拟基类被初始化[...]

  •   
  • 然后,直接基类被初始化   声明顺序显示在 base-specifier-list 中   (无论 mem-initializers 的顺序如何)。

  •   
  • 然后,非静态   数据成员按照在其中声明的顺序进行初始化   类定义(再次无论顺序如何    MEM-初始化)。

  •   
  • 最后,执行构造函数体的复合语句

  •   

即派生类&#39; ctor被称为&#34;首先,但在其复合语句(其函数体)之前,基类ctor必须完成。


我们可以看到这个顺序的一种方法是在派生类的ctor中使用 function-try-block

#include <iostream>

struct Base {
    Base() { throw "Base throwing\n"; }
};

struct Derived : Base{
    Derived()
    try : Base()
    {}
    catch(char const* p) {
        std::cout << p;
    }
};

int main() {
    try { Derived d; }catch(...){}
}

Live example

function-try-block 可以捕获基础和成员初始化期间发生的异常。异常是隐式传播的:由于无法构造/初始化基类/成员,因此无法构造/初始化(派生)对象。


对于析构函数,[class.dtor] / 8

  

执行析构函数的主体并销毁正文中分配的任何自动对象后,类X的析构函数调用X的直接非析构函数变体非静态数据成员,析构函数   对于X的直接基类,如果X是派生类最多的类,则它的析构函数调用X的虚拟基类的析构函数。

     

所有析构函数都被调用,好像它们是用合格的引用   name,即忽略更多派生类中任何可能的虚拟覆盖析构函数。基础和成员按照构造函数完成的相反顺序销毁。

如果析构函数是虚拟的,编译器(有时)无法知道必须在销毁站点/翻译单元调用哪些析构函数(最派生类型及其基础)。因此,析构函数本身必须调用基础和成员的破坏(至少,对于通过动态调度调用的虚拟析构函数)。

答案 1 :(得分:0)

正确的答案是选择1,但这可以通过实际运行您已输入的代码轻松验证