Manual lifetime management, alternative to placement new

Viewed 80

I have a piece of low-level code with callbacks: Init and Cleanup, and others. I don't control the arguments of the callbacks. I chose to use a global variable for managing the state, say class state.

Here's a simple construct I can use:

std::unique_ptr<state> s;
void Init() {
    s = make_unique<state>();
}
void Cleanup() {
    s.reset();
}

It's simple and self explanatory. The main drawback: it uses the heap, which means that performance is sacrificed, and the allocation can fail.

Alternatively, I can use a construct similar to the following:

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();
}

Now I don't use the heap, but it's much uglier and more error prone, and I'm not even sure whether there's an undefined behavior lurking somewhere. I might be able to encapsulate it in a wrapper class, but there's so much that can go wrong.

Is there any common construct to achieve what I'm trying to do, i.e. having a unique_ptr-like class without the heap allocation?

2 Answers

I think you can use a std::unique_ptr with a class with overloaded new and delete operators:

#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 will use the static overloads in global_type and therefore will not reach for any heap allocations and should be zero overhead.

Since aligned_storage is required to provide a storage fit to store the provided size and alignment, this should have no undefined behavior.

However, I'd recommend just using a std::optional as spectras mentioned in a comment unless there's a specific need for unique_ptr.

Expanding the comment into an answer:

C++17 has the perfect tool for this: std::optional.

It does exactly what you are doing, except it also tracks whether it currently has an object within the storage or not. Unless you are doing embedded and the additional size for the flag matters, it should be good:

#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;
    }
}

Note that the code here will be happy if you call Init twice, or if you call CleanUp twice. If you want to prevent it, you can add an assert(!currentState) at beginning of Init and assert(currentState) at beginning of CleanUp.

Related