【发布时间】:2015-07-16 00:24:04
【问题描述】:
我有两个函数局部静态对象,一和二。 One 的构造函数和析构函数都通过 GetTwo() 访问 Two:
#include <iostream>
struct One;
struct Two;
const One& GetOne();
const Two& GetTwo();
struct Two {
const char* value = "It's two!";
Two() { std::cout << "Two construct" << std::endl; }
~Two() { std::cout << "Two destruct" << std::endl; }
};
struct One {
One() {
std::cout << "One construct" << std::endl;
const char* twoval = GetTwo().value;
std::cout << "twoval is: " << twoval << std::endl;
}
~One() {
std::cout << "One destruct" << std::endl;
const char* twoval = GetTwo().value;
std::cout << "twoval is: " << twoval << std::endl;
}
};
const One& GetOne() {
static One one;
return one;
}
const Two& GetTwo() {
static Two two;
return two;
}
int main(void) {
GetOne();
}
我用 g++ 4.8.4 编译它: g++ -std=c++11 [文件名]
它输出:
One construct
Two construct
twoval is: It's two!
One destruct
twoval is: It's two!
Two destruct
它们的构造和销毁顺序相同!我读到,对于同一翻译单元中的 C++ 类的静态变量,破坏顺序总是与构造顺序相反。但我猜不是?或者,这是未定义的行为吗?
另外,我听说对于 C++11,C++ 委员会为函数局部静态变量添加了一些花哨的保证,例如线程安全。如果不是未定义,那么这种行为是这些保证的一部分吗? (这会很不错,因为它会阻止您使用 One 的析构函数使用已破坏的 Two 实例来打自己的脚。)如果 GetOne 和 GetTwo 在不同的翻译单元中,有什么保证?
编辑:
感谢到目前为止的 cmets,我现在看到一个对象仅在其构造函数返回后才被认为是构造的,而不是在它第一次进入时,所以实际上在 One 之前构造了 Two。
我还尝试阅读标准并在 C++11 标准第 6.7 节第 4 项中找到了这一点:
所有块范围变量的零初始化(8.5)静态 存储持续时间(3.7.1)或线程存储持续时间(3.7.2)是 在任何其他初始化发生之前执行。持续的 具有静态存储的块范围实体的初始化(3.6.2) 持续时间,如果适用,在它的块是第一个之前执行 进入。 ...这样的变量 在控件第一次通过其声明时被初始化; 这样的变量在其完成时被认为是初始化的 初始化。
对于破坏,6.7 将我们指向 3.6.3,它说:
如果完成构造函数或动态初始化一个 具有静态存储持续时间的对象在 另,第二个的析构函数的完成是有序的 在第一个析构函数启动之前。
所以如果我没看错的话:对于函数局部静态对象,它们的构造在运行时是“排序的”,基于函数被调用的顺序。而且,无论它们是在哪个翻译单元中定义的,它们都将按照与运行时相关的顺序相反的顺序被销毁。
这听起来对吗?这将使它成为静态订单初始化惨败的一个很好的解决方案。话虽如此,我认为您仍然可以使用以下代码自取其辱:
#include <iostream>
struct One;
struct Two;
const One& GetOne();
const Two& GetTwo();
void PrintOneValue(const One& one);
struct Two {
Two() { std::cout << "Two construct" << std::endl; }
~Two() {
std::cout << "start Two destruct" << std::endl;
PrintOneValue(GetOne());
std::cout << "end Two destruct" << std::endl;
}
};
struct One {
const char* value = "It's one!";
One() {
std::cout << "start One construct" << std::endl;
GetTwo();
std::cout << "end One construct" << std::endl;
}
~One() {
std::cout << "One destruct" << std::endl;
}
};
void PrintOneValue(const One& one) {
std::cout << "One's value is: " << one.value << std::endl;
}
const One& GetOne() {
static One one;
return one;
}
const Two& GetTwo() {
static Two two;
return two;
}
int main(void) {
GetOne();
}
哪些输出:
start One construct
Two construct
end One construct
One destruct
start Two destruct
One's value is: It's one!
end Two destruct
它在销毁后访问 One 的数据,因此未定义的行为。但至少它是确定性的。
【问题讨论】:
-
请注意,您调用
GetTwo之前One的静态实例已完全构造。 -
@CaptainObvlious 这没什么问题
-
@MattMcNabb 从没说过有。
-
@Captain Obvlious 谢谢,我想我明白了。我猜该标准规定对象必须以与它们的破坏顺序相反的方式完全构造。我们在二之前开始建设一,但是在一之前完成二的建设。所以二被认为是在一之前“被构造”的。
标签: c++ c++11 static-initialization