代码之家  ›  专栏  ›  技术社区  ›  Oktalist

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

  •  6
  • Oktalist  · 技术社区  · 7 年前

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

    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和标准会说“程序何时启动”、“线程何时启动”、“程序何时结束”和“线程何时结束”,但是这些概念在主线程上下文中是如何相互关联的呢?或者更精确地说,第一个线程和最后一个线程?

    在我的“真实”问题中, (线程本地)由记录器和的基类的析构函数使用。 酒吧 使用了记录器,所以这是一个静态破坏命令失败。其他线程被生成,但是 酒吧 (梅耶斯单体)在主线程中被构造和破坏。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不只是随机尝试。

    1 回复  |  直到 7 年前
        1
  •  5
  •   llllllllll    7 年前

    该标准保证 Foo (线程本地存储)在 Bar (静态存储):

    [basic.start.term]/2

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

    但是,对施工顺序没有保证。该标准只规定在第一次使用ODR之前,应该构造线程本地:

    [basic.stc.thread]/2

    具有线程存储持续时间的变量应在首次使用ODR之前初始化。

    推荐文章