C++11 thread_local 析构函数行为

JAT*_*rim 5 c++ destructor thread-local

我有以下情况:在标头“test.hpp”中我定义:

class ObjectA {
    public:
        ObjectA();
        ~ObjectA();
        static ObjectA & get_A();
};
class ObjectB {
    public:
        ~ObjectB();
        static ObjectB & get_B();
        void do_cleanup();
};
Run Code Online (Sandbox Code Playgroud)

在单独的编译单元中,我实现了 ObjectB:

#include "test.hpp"
#include <iostream>
ObjectB::~ObjectB() {
    std::cout<<"ObjectB dtor"<<std::endl;
}
ObjectB & ObjectB::get_B() {
    thread_local ObjectB b_instance;
    return b_instance;
}
void ObjectB::do_cleanup() {
    std::cout<<"Clearing up B garbage..."<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)

对象A:

#include "test.hpp"
#include <iostream>
ObjectA::ObjectA() {
    ObjectB::get_B(); <--dummy call to initialize thread_local ObjectB;
}
ObjectA::~ObjectA() {
     std::cout<<"ObjectA dtor"<<std::endl;
     ObjectB::get_B().do_cleanup(); // <-- is this undefined behaviour??
}
ObjectA & ObjectA::get_A() {
     thread_local ObjectA a_instance;
     return a_instance;
}
Run Code Online (Sandbox Code Playgroud)

最后是测试 main():

#include <thread>
#include "test.hpp"
int main() {
    std::thread check([](){
    ObjectA::get_A(); //<--dummy call just to initialize thread_local object.
    });
    check.join();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

上述程序是否表现良好,或者正在访问 objectB,它具有来自 ObjectA 析构函数的 thread_local 存储,而 ObjectA 析构函数也具有 thread_local 存储未定义的行为?如果是这样,为什么会损坏以及如何修复它?

我发现的最相关的问题

[编辑,@Soonts 回答]

在实际用例中,A 类是模板,相当复杂,而 B 类很大。A 对象使用shared_ptr<> 保存对B 的引用,并且B 的thread_locals 根据需要进行访问。(A 在主线程中构造并传递给工作线程)因此,在调用 ObjectA::get_A() 之前,工作线程可能不会调用 ObjectB::get_B()。

Soo*_*nts 6

该规范说了一些关于生命周期的事情:

\n\n

存储类别说明符

\n\n

线程存储持续时间。对象的存储在线程开始时分配,并在线程结束时释放。每个线程都有自己的对象实例。

\n\n

终止

\n\n

如果具有线程存储持续时间的对象的构造函数或动态初始化的完成顺序在另一个对象之前,则第二个对象的析构函数的完成顺序在第一个对象的析构函数的启动之前。

\n\n

现在回到你的代码。

\n\n

您构造 A,然后在构造函数中构造 B。因此,B 构造函数的完成发生在 A 构造函数完成之前。根据上面的内容,当线程即将退出时,它会先销毁A,然后再销毁B。根据规范的字母,您的代码是可以的。

\n\n

实际上,我\xe2\x80\x99m 不确定 C++ 编译器将规范实现到那么详细的程度。如果我编写该代码,我将\xe2\x80\x99 不会以这种方式使用 thread_local 对象。相反,我会将 B 放在 A 的非静态字段中。与依赖语言标准的这种细微差别相比,它\xe2\x80\x99 更简单,更可靠。

\n