可以使用静态字符数组(线程安全)来延长对象的寿命吗?
Can a static array of chars be (thread-safe) used to prolong life of an object?
我遇到了一些具有类似于以下功能的代码,使用了一些模板 class 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()
用于其他静态 classes 的析构函数中,这些静态 classes 稍后会被析构。 A
的复杂结构显然是为了延长 A
的生命周期而不会破坏任何其他对象,并且它本身永远不会被破坏。我注意到 https://en.cppreference.com/w/cpp/utility/program/exit 说
If a function-local (block-scope) static object was destroyed and then
that function is called from the destructor of another static object
and the control flow passes through the definition of that object (or
if it is used indirectly, via pointer or reference), the behavior is
undefined.
由于static storage和ptr不是'objects',我认为这条规则不会使第一个版本出现未定义行为,但它确实阻止了在函数内部构造一个static std::mutex
。因此,我的问题是:
- 根据 C++11 标准(或更新版本),鉴于
get_A
是从具有静态生命周期的对象的析构函数中调用的,是所有单线程程序中的第一个版本确实合法的情况还是可能会造成未定义的行为?
- 如何在不调用未定义行为且不必更改其他 class 对
get_A
的使用的情况下使该线程安全?我不希望为每个可能的 X
初始化代码,模板 A
被实例化,因为 A
被许多不同的类型实例化。除非那是唯一好的解决方案。
我找到了解决方法:
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没有自己的记忆)。
我遇到了一些具有类似于以下功能的代码,使用了一些模板 class 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()
用于其他静态 classes 的析构函数中,这些静态 classes 稍后会被析构。 A
的复杂结构显然是为了延长 A
的生命周期而不会破坏任何其他对象,并且它本身永远不会被破坏。我注意到 https://en.cppreference.com/w/cpp/utility/program/exit 说
If a function-local (block-scope) static object was destroyed and then that function is called from the destructor of another static object and the control flow passes through the definition of that object (or if it is used indirectly, via pointer or reference), the behavior is undefined.
由于static storage和ptr不是'objects',我认为这条规则不会使第一个版本出现未定义行为,但它确实阻止了在函数内部构造一个static std::mutex
。因此,我的问题是:
- 根据 C++11 标准(或更新版本),鉴于
get_A
是从具有静态生命周期的对象的析构函数中调用的,是所有单线程程序中的第一个版本确实合法的情况还是可能会造成未定义的行为? - 如何在不调用未定义行为且不必更改其他 class 对
get_A
的使用的情况下使该线程安全?我不希望为每个可能的X
初始化代码,模板A
被实例化,因为A
被许多不同的类型实例化。除非那是唯一好的解决方案。
我找到了解决方法:
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没有自己的记忆)。