我正试图理解在主线程上下文中初始化和销毁具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的顺序规则。考虑这两类:
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 Singleton,一个具有静态存储持续时间的块范围对象。
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
我早就料到了
福
将首先构造,因为它在命名空间范围内。我认为允许实现将初始化推迟到对象的第一次ODR使用。我不知道它可以延迟到块作用域static初始化之后,但我可以接受它。
现在我颠倒函数调用的顺序
主要的
:
int main() {
Foo::instance();
Bar::instance();
}
输出结果如下:
Foo
Bar
~Foo
~Bar
现在我已经移动了
福
第一次调用前的实例
Bar::instance
,初始化顺序如我所料。
但我认为这些对象应该按照它们初始化的相反顺序被销毁,而这似乎并没有发生。我错过了什么?
对于静态和线程本地存储时间对象的初始化和销毁,cppreference和标准会说“程序何时启动”、“线程何时启动”、“程序何时结束”和“线程何时结束”,但是这些概念在主线程上下文中是如何相互关联的呢?或者更精确地说,第一个线程和最后一个线程?
在我的“真实”问题中,
福
(线程本地)由记录器和的基类的析构函数使用。
酒吧
使用了记录器,所以这是一个静态破坏命令失败。其他线程被生成,但是
酒吧
(梅耶斯单体)在主线程中被构造和破坏。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不只是随机尝试。