我正在尝试理解在主线程的上下文中初始化和销毁具有静态存储持续时间和线程本地存储持续时间的命名空间作用域和块作用域对象的排序规则。考虑这两类:
struct Foo {
Foo() { std::cout << "Foon"; }
~Foo() { std::cout << "~Foon"; }
static Foo &instance();
};
struct Bar {
Bar() { std::cout << "Barn"; }
~Bar() { std::cout << "~Barn"; }
static Bar &instance();
};
除了静态instance
成员函数的实现之外,它们是相同的:
thread_local Foo t_foo;
Foo &Foo::instance() { return t_foo; }
Bar &Bar::instance() { static Bar s_bar; return s_bar; }
Bar
是Meyers单例,是一个具有静态存储持续时间的块范围对象。
Foo
的实例是一个具有线程本地存储持续时间的命名空间作用域对象。
现在main
功能:
int main() {
Bar::instance();
Foo::instance();
}
以下是GCC 8.1.0和Clang 5.0.0的输出:
Bar
Foo
~Foo
~Bar
现场试用:https://coliru.stacked-crooked.com/a/f83a9ec588aed921
我原以为Foo
会首先构建,因为它在名称空间范围内。我认为允许实现将初始化推迟到对象的第一次odr使用。我不知道它可以推迟到块作用域静态初始化之后,但我可以接受。
现在我颠倒main
:中函数调用的顺序
int main() {
Foo::instance();
Bar::instance();
}
这是输出:
Foo
Bar
~Foo
~Bar
现在,我已经将Foo
实例的第一次odr使用移到了第一次调用Bar::instance
之前,初始化的顺序与我预期的一样。
但我认为应该按照初始化的相反顺序销毁对象,这似乎没有发生。我错过了什么?
关于静态和线程本地存储持续时间的对象的初始化和销毁,cppreference和标准说的是"程序何时启动"、"线程何时开始"、"程序何时结束"one_answers"线程何时结束",但在主线程的上下文中,这些概念是如何相互关联的?或者更准确地说,第一根线和最后一根线?
在我的"真实"问题中,记录器使用Foo
(线程本地),而Bar
基类的析构函数使用记录器,所以这是一个静态破坏顺序的惨败。派生了其他线程,但Bar
(Meyers单例)是在主线程中构造和销毁的。如果我能理解排序规则,那么我就可以尝试解决"真正的"问题,而不必只是随意尝试。
标准保证Foo
(线程本地存储)的销毁在Bar
(静态存储)之前:
[基本开始期限]/2
在该线程内具有线程存储持续时间的所有初始化对象的析构函数的完成强烈发生在任何具有静态存储持续时间对象的析构函数的初始化之前。
但是,无法保证施工顺序。标准只说线程本地应该在第一次使用odr之前构建:
[基本.stc.thread]/2
具有线程存储持续时间的变量应在其首次odr使用之前初始化