手动生命周期管理,替代安置新

Manual lifetime management, alternative to placement new

我有一段带有回调的低级代码:InitCleanup,以及其他代码。我不控制回调的参数。我选择使用全局变量来管理状态,比如 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 一起使用,并重载 newdelete 运算符:

#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)