我有两个函数局部的静态对象,One和Two。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++委员会为函数局部静态变量添加了一些花哨的保证,如线程安全。如果不是未定义的话,那么这个行为是否是这些保证的一部分呢?(这将非常好,因为它可以防止您使用已销毁的Two实例来使用One的析构函数自杀。)如果GetOne和GetTwo在不同的翻译单元中,那么什么是保证的呢?
编辑: 感谢迄今为止的评论,我现在明白了一个对象只有在其构造函数返回时才被认为是构造完成的,而不是在首次进入时,所以Two实际上是在One之前构造的。
另外,我尝试阅读标准并在C++11标准的第6.7节第4项中找到了以下内容:
“所有具有静态存储期(3.7.1)或线程存储期(3.7.2)的块作用域变量的零初始化(8.5)在其他任何初始化之前都要执行。如果适用,块作用域实体的常量初始化(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的数据,这会导致未定义的行为。但至少它是可确定的。
One
完全构造之前您正在调用GetTwo
。 - Captain Obvlious