手动寿命管理,替代安置

问题描述

我有一段带有回调的低级代码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();
}

现在我不使用堆,但是它更丑陋并且更容易出错,而且我什至不确定在某个地方是否存在未定义的行为。我也许可以将其封装在包装器类中,但是有很多地方可能出错。

是否有任何通用的结构来实现我要完成的任务,即具有类似unique_ptr的类但没有堆分配?

解决方法

我认为您可以 std::unique_ptr与带有重载的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,则此处的代码将会很高兴。如果要防止出现这种情况,可以在assert(!currentState)的开头添加Init,在assert(currentState)的开头添加CleanUp