手动生命周期管理,替代安置新
Manual lifetime management, alternative to placement new
我有一段带有回调的低级代码:Init
和 Cleanup
,以及其他代码。我不控制回调的参数。我选择使用全局变量来管理状态,比如 class state
。
这是我可以使用的简单结构:
std::unique_ptr<state> s;
void Init() {
s = make_unique<state>();
}
void Cleanup() {
s.reset();
}
它简单明了。主要缺点:它使用堆,这意味着牺牲了性能,并且分配可能会失败。
或者,我可以使用类似于以下的结构:
alignas(state) unsigned char s_buf[sizeof(state)];
void Init() {
state* s = new(s_buf) state;
}
void Cleanup() {
state* s = reinterpret_cast<state*>(s_buf);
s->~T();
}
现在我不使用堆,但它更丑陋且更容易出错,我什至不确定是否有未定义的行为潜伏在某处。我也许可以将它封装在一个包装器中 class,但是可能会出错的地方太多了。
是否有任何共同的构造来实现我正在尝试做的事情,即在没有堆分配的情况下拥有类似 unique_ptr
的 class?
我认为你 可以 将 std::unique_ptr
与 class 一起使用,并重载 new
和 delete
运算符:
#include <cstddef>
#include <type_traits>
#include <memory>
template <class T>
struct global_type : T {
static void* operator new (size_t) {
static std::aligned_storage_t<sizeof(global_type), alignof(global_type)> m_store;
return &m_store;
}
static void operator delete(void*) {
}
};
struct state {
int foo;
};
std::unique_ptr<global_type<state>> ptr;
void Init() {
ptr = std::make_unique<global_type<state>>();
ptr->foo = 42;
}
void CleanUp() {
ptr.reset();
}
std::unique_ptr
将使用 global_type
中的静态重载,因此不会达到任何堆分配并且应该是零开销。
由于 aligned_storage
需要提供合适的存储空间来存储所提供的大小和对齐方式,因此这应该没有未定义的行为。
但是,我建议只使用 std::optional
作为评论中提到的光谱,除非有特定需要 unique_ptr
。
将评论扩展为答案:
C++17 有完美的工具:std::optional
.
它做的正是你正在做的,除了它还跟踪它当前是否在存储中有一个对象。除非你正在做嵌入式并且标志的额外大小很重要,否则应该很好:
#include <optional>
struct state {
int foo;
};
static std::optional<state> currentState;
void Init() {
currentState = state{ 42 };
}
void CleanUp() {
currentState = std::nullopt;
}
void doStuff() {
currentState->foo += 12;
}
void doStuffOnlyIfInitialized() {
if (currentState) {
currentState->foo += 12;
}
}
请注意,如果您调用 Init
两次,或者如果您调用 CleanUp
两次,此处的代码将会正常运行。如果你想防止它,你可以在Init
的开头添加一个assert(!currentState)
,在CleanUp
的开头添加一个assert(currentState)
。
我有一段带有回调的低级代码:Init
和 Cleanup
,以及其他代码。我不控制回调的参数。我选择使用全局变量来管理状态,比如 class state
。
这是我可以使用的简单结构:
std::unique_ptr<state> s;
void Init() {
s = make_unique<state>();
}
void Cleanup() {
s.reset();
}
它简单明了。主要缺点:它使用堆,这意味着牺牲了性能,并且分配可能会失败。
或者,我可以使用类似于以下的结构:
alignas(state) unsigned char s_buf[sizeof(state)];
void Init() {
state* s = new(s_buf) state;
}
void Cleanup() {
state* s = reinterpret_cast<state*>(s_buf);
s->~T();
}
现在我不使用堆,但它更丑陋且更容易出错,我什至不确定是否有未定义的行为潜伏在某处。我也许可以将它封装在一个包装器中 class,但是可能会出错的地方太多了。
是否有任何共同的构造来实现我正在尝试做的事情,即在没有堆分配的情况下拥有类似 unique_ptr
的 class?
我认为你 可以 将 std::unique_ptr
与 class 一起使用,并重载 new
和 delete
运算符:
#include <cstddef>
#include <type_traits>
#include <memory>
template <class T>
struct global_type : T {
static void* operator new (size_t) {
static std::aligned_storage_t<sizeof(global_type), alignof(global_type)> m_store;
return &m_store;
}
static void operator delete(void*) {
}
};
struct state {
int foo;
};
std::unique_ptr<global_type<state>> ptr;
void Init() {
ptr = std::make_unique<global_type<state>>();
ptr->foo = 42;
}
void CleanUp() {
ptr.reset();
}
std::unique_ptr
将使用 global_type
中的静态重载,因此不会达到任何堆分配并且应该是零开销。
由于 aligned_storage
需要提供合适的存储空间来存储所提供的大小和对齐方式,因此这应该没有未定义的行为。
但是,我建议只使用 std::optional
作为评论中提到的光谱,除非有特定需要 unique_ptr
。
将评论扩展为答案:
C++17 有完美的工具:std::optional
.
它做的正是你正在做的,除了它还跟踪它当前是否在存储中有一个对象。除非你正在做嵌入式并且标志的额外大小很重要,否则应该很好:
#include <optional>
struct state {
int foo;
};
static std::optional<state> currentState;
void Init() {
currentState = state{ 42 };
}
void CleanUp() {
currentState = std::nullopt;
}
void doStuff() {
currentState->foo += 12;
}
void doStuffOnlyIfInitialized() {
if (currentState) {
currentState->foo += 12;
}
}
请注意,如果您调用 Init
两次,或者如果您调用 CleanUp
两次,此处的代码将会正常运行。如果你想防止它,你可以在Init
的开头添加一个assert(!currentState)
,在CleanUp
的开头添加一个assert(currentState)
。