主线程中块作用域静态与命名空间作用域 thread_local 的初始化和销毁​​顺序

Order of initialization and destruction of block-scope static vs. namespace-scope thread_local in main thread

我试图了解在主线程上下文中具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的初始化和销毁​​的顺序规则。考虑这两个 classes:

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 使用移动到第一次调用 Bar::instance 之前,并且初始化顺序符合我的预期。

但我认为对象应该按照与初始化相反的顺序销毁,但这似乎并没有发生。我错过了什么?

关于静态和线程本地存储持续时间对象的初始化和销毁​​,cppreference 和标准说 "when the program starts"、"when the thread starts"、"when the program ends" 和 "when the thread ends",但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,第一个线程和最后一个线程?

在我的"real"问题中,记录器使用Foo(线程本地),Bar的基class的析构函数使用记录器,所以这是静态销毁命令的惨败。生成了其他线程,但 Bar(Meyers 单例)在主线程中构造和销毁。如果我能理解排序规则,那么我就可以尝试解决 "real" 问题,而不必只是随机尝试。

标准保证Foo(线程本地存储)的销毁在Bar(静态存储)之前:

[basic.start.term]/2

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 之前构造:

[basic.stc.thread]/2

A variable with thread storage duration shall be initialized before its first odr-use