为什么这些函数局部静态对象的销毁顺序不是它们初始化顺序的倒序?
Why is the order of destruction of these function-local static objects NOT the inverse of their order of initialization?
我有两个函数局部静态对象,一和二。 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 在不同的翻译单元中,有什么保证?
编辑:
感谢到目前为止的评论,我现在看到一个对象只有在它的构造函数returns之后才被认为是构造的,而不是在它第一次进入时才被认为是构造的,所以二实际上是在一之前构造的。
我还尝试阅读标准并在 C++11 标准第 6.7 节第 4 项中找到了这个:
The zero-initialization (8.5) of all block-scope variables with static
storage duration (3.7.1) or thread storage duration (3.7.2) is
performed before any other initialization takes place. Constant
initialization (3.6.2) of a block-scope entity with static storage
duration, if applicable, is performed before its block is first
entered. ...such a variable
is initialized the first time control passes through its declaration;
such a variable is considered initialized upon the completion of its
initialization.
对于破坏,6.7 将我们指向 3.6.3,它说:
If the completion of the constructor or dynamic initialization of an
object with static storage duration is sequenced before that of
another, the completion of the destructor of the second is sequenced
before the initiation of the destructor of the first.
所以如果我没看错的话:对于函数局部静态对象,它们的构造在运行时是 "sequenced",基于调用函数的顺序。而且,无论它们在哪个翻译单元中定义,它们都将按照与运行时相关的顺序相反的顺序被破坏。
这听起来对吗?这将使它成为静态订单初始化失败的一个很好的解决方案。也就是说,我认为您仍然可以用下面的代码搬起石头砸自己的脚:
#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
它在一个人的数据被破坏后访问它,所以是未定义的行为。但至少它是确定性的。
将您的 ctor 更改为:
One() {
std::cout << "Start One construct" << std::endl;
const char* twoval = GetTwo().value;
std::cout << "twoval is: " << twoval << std::endl;
std::cout << "Finish One construct" << std::endl;
}
现在您会看到 Two
在 One
之前完成构建。所以 Two
在 One
之前被注册销毁,并在之后被销毁,因为它实际上是(完全)首先构建的。
Start One construct
Two construct
twoval is: It's two!
Finish One construct
One destruct
twoval is: It's two!
Two destruct
C++14 [basic.start.term] 中的实际标准文本是:
If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first. [Note: This definition permits concurrent destruction. —end note ]
在您的代码中,two
是在 one
的构造函数中构造的。因此 two
的构造函数的 完成 在 one
的构造函数完成之前排序。
所以 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 在不同的翻译单元中,有什么保证?
编辑:
感谢到目前为止的评论,我现在看到一个对象只有在它的构造函数returns之后才被认为是构造的,而不是在它第一次进入时才被认为是构造的,所以二实际上是在一之前构造的。
我还尝试阅读标准并在 C++11 标准第 6.7 节第 4 项中找到了这个:
The zero-initialization (8.5) of all block-scope variables with static storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. ...such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization.
对于破坏,6.7 将我们指向 3.6.3,它说:
If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.
所以如果我没看错的话:对于函数局部静态对象,它们的构造在运行时是 "sequenced",基于调用函数的顺序。而且,无论它们在哪个翻译单元中定义,它们都将按照与运行时相关的顺序相反的顺序被破坏。
这听起来对吗?这将使它成为静态订单初始化失败的一个很好的解决方案。也就是说,我认为您仍然可以用下面的代码搬起石头砸自己的脚:
#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
它在一个人的数据被破坏后访问它,所以是未定义的行为。但至少它是确定性的。
将您的 ctor 更改为:
One() {
std::cout << "Start One construct" << std::endl;
const char* twoval = GetTwo().value;
std::cout << "twoval is: " << twoval << std::endl;
std::cout << "Finish One construct" << std::endl;
}
现在您会看到 Two
在 One
之前完成构建。所以 Two
在 One
之前被注册销毁,并在之后被销毁,因为它实际上是(完全)首先构建的。
Start One construct
Two construct
twoval is: It's two!
Finish One construct
One destruct
twoval is: It's two!
Two destruct
C++14 [basic.start.term] 中的实际标准文本是:
If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first. [Note: This definition permits concurrent destruction. —end note ]
在您的代码中,two
是在 one
的构造函数中构造的。因此 two
的构造函数的 完成 在 one
的构造函数完成之前排序。
所以 one
的析构函数完成顺序是在 two
的析构函数完成之前,这解释了你所看到的。