对象的构造和析构顺序

通过一道CppQuiz的题来使用C++14标准描述C++的对象在继承情况下构造和析构的顺序,以及在对象构造/析构时抛出异常。

先放标准:

对象构造时的执行顺序

In a non-delegating constructor, initialization proceeds in the following order:

  • First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes,where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
  • Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list(regardless of the order of the mem-initializers).
  • Then, non-static data members are initialized in the order they were declared in the class definition(again regardless of the order of the mem-initializers).
  • Finally, the compound-statement of the constructor body is executed.

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed inthe reverse order of initialization. - end note ]

对象析构时的执行顺序

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

构造和析构时抛出异常

An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <exception>

int x = 0;

class A {
public:
A() {
std::cout << 'a';
if (x++ == 0) {
throw std::exception();
}
}
~A() { std::cout << 'A'; }
};

class B {
public:
B() { std::cout << 'b'; }
~B() { std::cout << 'B'; }
A a;
};

void foo() { static B b; }

int main() {
try {
foo();
}
catch (std::exception &) {
std::cout << 'c';
foo();
}
}

Answer:acabBA

  1. 首先,调用foo()foo函数体中创建了一个static B对象,因为没有提供初始化式,所以会调用B的默认构造函数。
  2. 根据对象构造时的执行顺序,来初始化基类(如果有的话)和数据成员。因为类B没有基类且只有一个A类的数据成员a,按照上面的规则,会先执行成员a的初始化,因为也没有提供初始化式,所以也会调用A的默认构造函数。
  3. A的默认构造函数中输出a,然后检查此时x++ == 0的结果为true,所以会在A的构造函数中抛出一个异常。因为当在构造函数抛出一个异常时,会对其所有完全构造的子对象(不包括类似union的类的变体成员)执行构造函数,因此,A中并没有任何数据成员,所以会直接结束类A的构造,不会执行任何析构函数。
  4. catch中捕获到异常,并输出一个c
  5. 再次执行foo(),然后依次执行B->A的构造,先调用B中数据成员a的的构造函数。(输出a),由于上面执行过了x++,所以此时x++==0位false,不会抛出异常,A的对象构造成功。
  6. 执行B的构造函数体,输出b
  7. 程序结束时开始析构B类对象b,根据对象析构时的执行顺序来执行(和构造反序)
  8. 根据上面的规则,会先调用b的析构函数输出B
  9. 然后再调用b的A类数据成员a的析构函数输出A
全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:对象的构造和析构顺序
文章作者:查利鹏
发布时间:2017年02月19日 18时04分
本文字数:本文一共有2.5k字
原始链接:https://imzlp.com/posts/16550/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!