我有以下情况:在标头“ test.hpp”中定义:
class ObjectA {
public:
ObjectA();
~ObjectA();
static ObjectA & get_A();
};
class ObjectB {
public:
~ObjectB();
static ObjectB & get_B();
void do_cleanup();
};
然后在单独的编译单元中实现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;
}
ObjectA:
#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;
}
最后是一个测试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;
}
以上程序是否行为良好或正在访问objectB,而ObjectB具有来自ObjectA析构函数的thread_local存储,而后者也具有thread_local存储未定义的行为? 如果是这样,为什么会损坏?我该如何解决?
[编辑,@ Soonts回答]
在实际用例中,A类是模板,一个非常复杂的类,而B类则很大。一个对象使用shared_ptr <>保存对B的引用,并且B的thread_locals可以根据需要进行访问。 (A在主线程中构造并传递给工作程序)因此,在调用ObjectA :: get_A()之前,工作程序线程可能不会调用ObjectB :: get_B()。
答案 0 :(得分:1)
该规范说明了有关寿命的几件事:
线程存储持续时间。对象的存储在线程开始时分配,并在线程结束时释放。每个线程都有自己的对象实例。
如果构造函数的 completion 或具有线程存储持续时间的对象的动态初始化先于另一个的顺序进行了排序,则第二个析构函数的完成将在启动的析构函数之前进行排序。第一个。
现在回到您的代码。
您构造A,在构造器中构造B。因此,B构造器的完成发生在A构造器的完成之前。根据以上所述,当线程即将退出时,它将首先破坏A,然后破坏B。根据规范的字母,您的代码可以。
实际上,我不确定C ++编译器是否会实现详细说明。如果我正在编写该代码,则不会以这种方式使用thread_local对象。取而代之的是,我将B放在A的非静态字段中。它比依赖于这种语言标准的细微差别更简单,而且IMO更可靠。