我正在尝试了解在主线程的上下文中使用静态存储持续时间和线程本地存储持续时间来初始化和销毁命名空间范围和 block 范围对象的顺序规则。考虑这两个类:
struct Foo {
Foo() { std::cout << "Foo\n"; }
~Foo() { std::cout << "~Foo\n"; }
static Foo &instance();
};
struct Bar {
Bar() { std::cout << "Bar\n"; }
~Bar() { std::cout << "~Bar\n"; }
static Bar &instance();
};
除了它们的静态实例
成员函数的实现之外,它们是相同的:
thread_local Foo t_foo;
Foo &Foo::instance() { return t_foo; }
Bar &Bar::instance() { static Bar s_bar; return s_bar; }
Bar
是一个 Meyers 单例,一个具有静态存储持续时间的 block 范围对象。
Foo
的实例是具有线程本地存储持续时间的 namespace 范围对象。
现在是 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 使用。我不知道它可以推迟到 block 作用域静态初始化之后,但我可以接受。
现在我颠倒了 main
中函数调用的顺序:
int main() {
Foo::instance();
Bar::instance();
}
这是输出:
Foo
Bar
~Foo
~Bar
现在我已经将 Foo
实例的第一次 odr-use 移动到第一次调用 Bar::instance
之前,并且初始化顺序与我一样预期。
但我认为对象应该按照与初始化相反的顺序销毁,但这似乎并没有发生。我错过了什么?
关于静态和线程本地存储持续时间的对象的初始化和销毁,cppreference 和标准说“程序启动时”,“线程启动时”,“程序结束时”,以及“当线程结束时”,但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,第一个线程和最后一个线程?
在我的“真实”问题中,Foo
(线程本地)被记录器使用,Bar
的基类的析构函数使用记录器,所以这是静态销毁命令的惨败。生成了其他线程,但在主线程中构造和销毁了 Bar
(Meyers 单例)。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不必求助于随机尝试。
最佳答案
标准保证 Foo
(线程本地存储)在 Bar
(静态存储)之前被销毁:
The completions of the destructors for all initialized objects with thread storage duration within that thread strongly happen before the initiation of the destructors of any object with static storage duration.
但是,无法保证施工顺序。该标准只说线程本地应该在它的第一次 odr-use 之前构造:
A variable with thread storage duration shall be initialized before its first odr-use
关于c++ - 主线程中 block 作用域静态与命名空间作用域 thread_local 的初始化和销毁顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53999850/