6

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

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 单例)在主线程中构造和销毁。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不必只是随机尝试。

4

1 回答 1

5

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

[basic.start.term]/2

在该线程中具有线程存储持续时间的所有已初始化对象的析构函数的完成强烈发生在任何具有静态存储持续时间的对象的析构函数的启动之前。

但是,不能保证施工顺序。该标准只说应该在第一次使用 odr 之前构造线程本地:

[basic.stc.thread]/2

具有线程存储持续时间的变量应在其第一次使用 odr 之前进行初始化

于 2019-01-02T02:32:50.210 回答