我试图了解在主线程上下文中具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的初始化和销毁的排序规则。考虑这两个类:
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();
};
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-use 移到了第一次调用 to 之前Bar::instance
,并且初始化的顺序与我预期的一样。
但我认为对象应该按照初始化的相反顺序被销毁,这似乎没有发生。我错过了什么?
关于静态和线程本地存储持续时间的对象的初始化和销毁,cppreference 和标准说诸如“程序何时启动”、“线程何时启动”、“程序何时结束”和“何时线程结束”,但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,第一个线程和最后一个线程?
在我的“真实”问题中,Foo
记录器使用(线程本地),而基类的析构函数Bar
使用记录器,所以这是一个静态破坏顺序惨败。产生了其他线程,但是Bar
(Meyers 单例)在主线程中构造和销毁。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不必只是随机尝试。