我使用一些模板化类A
遇到了一些具有类似于以下功能的代码:
template<typename X>
A<X>* get_A() {
static char storage[sizeof(A<X>)];
static A<X>* ptr = nullptr;
if(!ptr) {
new (reinterpret_cast<A<X>*>(storage)) A<X>();
ptr = reinterpret_cast<A<X>*>(storage);
}
return ptr;
}
我需要确保此初始化线程的安全,因此将其更改为:
A<X>* get_A() {
static A<X> a;
return &a;
}
这会导致段错误:get_A()
用于其他静态类的析构函数中,这些静态类随后将被析构。 A
的复杂构造显然可以延长A
的寿命,而不会破坏任何其他对象,并且它本身从未被破坏。我注意到https://en.cppreference.com/w/cpp/utility/program/exit说
如果局部函数(块范围)静态对象被破坏,然后 该函数是从另一个静态对象的析构函数调用的 并且控制流通过该对象的定义(或 如果通过指针或引用间接使用它,则行为是 未定义。
由于静态存储区和ptr都不是“对象”,因此我认为该规则不会使第一个版本成为未定义的行为,但确实会阻止在函数内部构造static std::mutex
。因此,我的问题是:
get_A
是从具有静态生存期的对象的析构函数中调用的,则它在所有情况下实际上都是单线程程序中的第一个版本合法还是会施加不确定的行为?get_A
的使用的情况下使此线程安全?我不希望为模板X
实例化的每个可能的A
都具有初始化代码,因为A
用许多不同的类型实例化。除非那是唯一的好办法。答案 0 :(得分:0)
我找到了解决方案:
A* get_A()
{
static typename std::aligned_storage<sizeof(A), alignof(A)>::type storage;
static A* ptr = new (reinterpret_cast<A*>(&storage)) A();
return ptr;
}
我将问题中使用的char
数组更改为std::aligned_storage
,以确保该数组具有正确的对齐方式。在C ++ 17中,可能需要std::launder
,但我使用的是C ++ 11。输入A
,当然可以像原始问题中那样对该函数进行模板化,但是我将示例保持得尽可能简单。
这仍然有点hack,但是据我所知这是线程安全的,它允许初始化静态对象,而不会破坏它并且不会泄漏内存(当然,只要A不t自己的记忆)。