考虑以下计划。
struct s { ~s(); };
void f()
{
static s a;
}
struct t { ~t() { f(); } };
int main()
{
static s b;
static t c;
}
我正在试图找出关于破坏静态对象的标准保证,但我发现C ++ 03 [basic.start.term]的文本相当不足。
是否定义了程序的行为?如果是这样,静态对象a
,b
和c
的破坏顺序是什么?如果s::~s
抛出异常,会发生什么?请解释您的推理,最好使用标准中的引号。
答案 0 :(得分:8)
以下Objects
指的是静态存储持续时间的对象。
全局命名空间中的对象在main之前创建。
同一编译单元中的对象按定义顺序创建 订单在不同的编译单元中未定义。
命名空间中的对象是在访问该命名空间中的任何函数/变量之前创建的。这可能是也可能不是主要的。
首次使用时会创建函数中的对象。
对象以创建的相反顺序销毁。请注意,创建顺序由其CONSTRUCTOR的完成定义(而不是在调用时)。因此,在构造函数中创建另一个'y'的一个对象'x'将导致首先构造'y'。
如果没有创建,那么它们就不会被销毁。
是否定义了程序的行为?
所以是的,订单定义明确
b: Created
c: Created
c: Destroyed Start
a: Created (during destruction of C)
c: Destroyed End
a: Destroyed (a was created after b -> destroyed before b)
b: Destroyed
修改代码以查看:
#include <iostream>
struct s
{
int mx;
s(int x): mx(x) {std::cout << "S(" << mx << ")\n";}
~s() {std::cout << "~S(" << mx << ")\n";}
};
void f()
{
static s a(3);
}
struct t
{
int mx;
t(int x): mx(x) { std::cout << "T(" << mx << ")\n";}
~t()
{ std::cout << "START ~T(" << mx << ")\n";
f();
std::cout << "END ~T(" << mx << ")\n";
}
};
int main()
{
static s b(1);
static t c(2);
}
输出是:
$ ./a.exe
S(1)
T(2)
Start ~T(2)
S(3)
END ~T(2)
~S(3)
~S(1)
答案 1 :(得分:5)
如前所述,析构函数调用的顺序与构造函数(3.6.3/1
)的完成顺序完全相反。换句话说(3.8/1
),静态存储持续时间的对象的生命周期的停止与静态存储持续时间的对象的生命周期的开始相反。所以这一切都归结为他们的构造函数被调用。假设Printer
是在以下示例中在其构造函数中输出某些内容的类型。
命名空间范围的对象(全局和用户定义)在任何情况下都是在第一次使用在(3.6.2/3
)中定义对象的相同转换单元中定义的任何函数或变量之前创建的。这个延迟初始化(在调用main之后)必须遵守该对象定义的相同转换单元中关于其他对象定义的定义顺序。 (3.6.2/1
)。
翻译单元1 :
void f() { }
extern Printer a;
Printer b("b");
Printer a("a");
extern Printer c;
翻译单元2:
Printer c("c");
void f();
如果我们使用f
,则不一定会强制创建c
,因为f未在定义c
的翻译单元中定义。a
是在b
之后创建,因为它稍后定义。
块控制范围(局部静态)的对象是在控件首先通过其定义或首次为POD(6.7/4
)输入块时创建的。如果创建无法成功(如果出现异常)(6.7/4
),则下次控制传递时会再次尝试启动生命周期。
void g() { static Print p("P"); }
struct A {
A() {
static int n;
if(n++ == 0) throw "X";
cout << "A";
g();
}
};
void f() {
try { static A a; } catch(char const *x) { cout << x; }
}
此代码段输出“XAP”。
对于静态数据成员,同一规则适用于在同一翻译单元(3.6.2/1
)内根据其定义顺序的初始化顺序。这是因为它的规则被公式化为“在命名空间范围中定义的对象......”而不是“命名空间范围的对象......”。在C ++ 03中,延迟初始化(延迟构造直到从其转换单元使用变量/函数)仅允许名称空间范围的对象which was not intended。 C ++ 0x也允许静态数据成员(“具有静态存储持续时间的非本地变量”)。
因此,通过采用上述规则并考虑到破坏顺序实际上是由构造函数的完成而不是从它们的开始确定的,我们将得到命令~c
~a
{{1 }}。
如果~b
抛出异常,C ++ 0x表示调用了s::~s
,并且最终导致terminate()
被破坏并且生命终止c
而未完成它的析构函数,如果它抛出异常。我在C ++ 03标准中找不到任何指定的内容。它似乎只指定对于非局部静态而不是像C ++ 0x那样的块范围静态。