成员构造函数和析构函数调用的顺序


120

哦,C ++专家们,我寻求您的智慧。跟我说标准话,告诉我C ++是否保证以下程序:

#include <iostream>
using namespace std;

struct A
{
    A() { cout << "A::A" << endl; }
    ~A() { cout << "A::~" << endl; }
};

struct B
{
    B() { cout << "B::B" << endl; }
    ~B() { cout << "B::~" << endl; }
};

struct C
{
    C() { cout << "C::C" << endl; }
    ~C() { cout << "C::~" << endl; }
};

struct Aggregate
{
    A a;
    B b;
    C c;
};

int main()
{
    Aggregate a;
    return 0;
}

将永远产生

A::A
B::B
C::C
C::~
B::~
A::~

换句话说,是否保证成员按声明顺序初始化并以相反的顺序销毁?


8
当类变得庞大而笨拙时,这是造成细微错误的合理普遍原因。当您有50个数据成员,并且其中的许多成员在构造函数初始化器列表中进行了初始化时,可以很容易地假定构造顺序就是初始化器列表中的顺序。毕竟,代码编写者已经仔细地排序了列表……不是吗?
2010年

Answers:


138

换句话说,是否保证成员按声明顺序初始化并以相反的顺序销毁?

双方都同意。见12.6.2

6初始化应按以下顺序进行:

  • 首先,并且仅对于如下所述的最大派生类的构造函数,虚拟基类应按照它们在基类的有向无环图的深度优先从左到右遍历时出现的顺序进行初始化,其中“左“从右到右”是基类名称在派生类base-specifier-list中的出现顺序。

  • 然后,直接基类应按照它们出现在base-specifier-list中的声明顺序进行初始化(与mem-initializers的顺序无关)。

  • 然后,非静态数据成员应按照它们在类定义中声明的顺序进行初始化(同样,与内存初始化程序的顺序无关)。

  • 最后,执行构造函数主体的复合语句。[注意:声明顺序是强制性的,以确保以相反的初始化顺序销毁基础和成员子对象。—尾注]


2
如果我没记错的话,对两者都是肯定的...将其视为堆栈。首先按下,最后弹出。因此,在实例化第一个实例时,它会按堆栈顺序推入内存。然后,第二个被推,第二个被推,依此类推。然后,在销毁实例时,程序将寻找第一个销毁的对象,最后一个被推送的对象。但是用这种方式来解释它可能是错误的,但这是我在进行C / C ++和ASM时所学的方法。
Will Marcouiller

29

是的,它们是(是非静态成员)。初始化(构造)请参见12.6.2 / 5,销毁请参见12.4 / 6。


10

是的,该标准保证对象以与创建对象相反的顺序被破坏。原因是一个对象可能使用另一个对象,因此依赖于它。考虑:

struct A { };

struct B {
 A &a;
 B(A& a) : a(a) { }
};

int main() {
    A a;
    B b(a);
}

如果a是之前破坏b,然后b将举行无效成员引用。通过按照创建对象的相反顺序破坏对象,我们保证正确破坏。


我从未真正考虑过此规则也适用于作用域成员的销毁顺序!
yano

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.