我知道这个问题有明确的答案:首先调用基类构造函数,然后调用派生类构造函数。
但我并不完全理解"叫做#34;。这是否意味着构造函数的使用开始,或构造函数的使用完成?换句话说,下面的代码有两种可能的顺序:
BaseClass构造函数启动 - > BaseClass构造函数完成 - > DerivedClass构造函数启动 - > DerivedClass构造函数完成。
DerivedClass构造函数启动 - > BaseClass构造函数启动 - > BaseClass构造函数完成 - > DerivedClass构造函数完成。
哪一个应该是正确的顺序?如果1是正确的,那么编译器在初始化DerivedClass实例之前如何知道调用BaseClass构造函数?
似乎案例2是正确的:"称为"应该意味着完成构造函数。后续问题是析构函数如何?我知道标准答案是"派生类的析构函数首先被调用"。那么这是正确的顺序:
由于
class BaseClass {
public:
BaseClass() {
cout << "BaseClass constructor." << endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() : BaseClass() {
cout << "DerivedClass constructor." << endl;
}
};
int main() {
DerivedClass dc;
}
答案 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(...){}
}
function-try-block 可以捕获基础和成员初始化期间发生的异常。异常是隐式传播的:由于无法构造/初始化基类/成员,因此无法构造/初始化(派生)对象。
对于析构函数,[class.dtor] / 8
执行析构函数的主体并销毁正文中分配的任何自动对象后,类
X
的析构函数调用X
的直接非析构函数变体非静态数据成员,析构函数 对于X
的直接基类,如果X
是派生类最多的类,则它的析构函数调用X
的虚拟基类的析构函数。所有析构函数都被调用,好像它们是用合格的引用 name,即忽略更多派生类中任何可能的虚拟覆盖析构函数。基础和成员按照构造函数完成的相反顺序销毁。
如果析构函数是虚拟的,编译器(有时)无法知道必须在销毁站点/翻译单元调用哪些析构函数(最派生类型及其基础)。因此,析构函数本身必须调用基础和成员的破坏(至少,对于通过动态调度调用的虚拟析构函数)。
答案 1 :(得分:0)
正确的答案是选择1,但这可以通过实际运行您已输入的代码轻松验证