Question

I'm creating a generic AtomManager<T> container that can store Atom<T> objects.

enum State { Alive, Dead, Unused };
template<class T> struct Atom
{
    T impl;
    int index, counter;
    State state;
};

I want impl not to be on the heap, as I will store Atom<T> instances contiguously in the manager.

AtomManager<T> stores atoms in an std::vector<Atom<T>> like this:

| A | A | A | A | A | A | U | U | U | U |

Where A means alive, and U means unused. When the user calls AtomManager<T>::refresh(), all atoms with state equal to State::Dead will be moved at the end of the storage, and then they will be set to State::Unused. Example:

| A | A | A | A | A | A | U | U | U | U |

// some atoms die

| A | D | A | D | A | A | U | U | U | U |

// user calls refresh()

| A | A | A | A | U | U | U | U | D | D |

// after refresh()

| A | A | A | A | U | U | U | U | U | U |

To create atoms, I have a function that matches the T constructor signature thanks to variadic templates and constructs a T in the first unused atom starting from the beginning of the storage.

The problem is that T has to be default-constructible (because I call resize() on the std::vector). But that's something I don't require, as I only care about Atom<T>::impl when the state is either State::Alive or State::Dead. And if the atom is alive or dead, it means that the user previously constructed it using the previously mentioned variadic function.

I do, however, care about Atom<T>::index and Atom<T>::counter when the atom is unused (and Atom<T>::impl is garbage).

I do not care about the state of Atom<T>::impl when the atom is unused.

The compiler, however, does. I cannot use AtomManager<T> when T is not default-constructible.

I tried using an union inside Atom<T>:

union { T impl; char dummy; };

...but I couldn't get it to work properly.

How can I store a T non-constructed uninitialized instance inside Atom<T>?

I do not care about its state. I'm sure I will construct it properly before accessing it. But when the atom is unused, I want it to be in an undefined state.

What's the best way of achieving this? I don't want Atom<T>::impl to be stored on the heap.

I don't want to introduce additional dependencies. I don't need to query the state of Atom<T>::impl as I know when it's safe to access it.

Was it helpful?

Solution 2

aligned_storage, placement new and explicit destructor calls to the rescue!

enum State { Alive, Dead, Unused };
template<class T> struct Atom
{
    typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type impl;
    T& data() { return *reinterpret_cast<T*>(&impl); }
    T const& data() const { return *reinterpret_cast<T*>(&impl); }

    int index, counter;
    State state;

    template<typename... V>
    void create(V&&... args)
    {
        new(static_cast<void*>(&impl))T(std::forward<V>(args)...);
        state = Alive;
    }

    void destroy() { data().~T(); state = Unused; }

    ~Atom() { if(state != Unused) destroy(); }

    Atom(Atom const& other)
        : index(other.index),
        counter(other.counter),
        state(other.state)
    {
        if(state != Unused)
            new(static_cast<void*>(&impl))T(other.data());
    }

    Atom(Atom&& other)
        : index(other.index),
        counter(other.counter),
        state(other.state)
    {
        if(state != Unused)
            new(static_cast<void*>(&impl))T(std::move(other.data()));
    }
};

The basic idea is to start with providing properly aligned uninitialized storage. Then, when you whish to create an object, a variadic template forwards all your arguments to a placement new expression, which will basically call the constructor on that storage. Finally, when you destroy it (or the destructor of Atom is called while it is not yet Unused again), the destructor is called so that the memory does not represent an object anymore.

The humongous copy and move constructors are needed, so that copies will map to copies of the Ts correctly. Writing assignment operators has been omitted for brevity (such as it is).

OTHER TIPS

The simple answer is to use boost::optional, but you say you don't want that for some reason.

To roll your own optional type, you'll want a byte array, suitably aligned for T. In C++11 or later, that's simple:

alignas(T) char bytes[sizeof(T)];

If you're stuck with a historic dialect, then you might have to use compiler-specific extensions to specify the alignment, or just hope for the best.

Now you can create the object using placement-new:

T * impl = new(bytes) T(...);

the easiest way to access it is via a function:

T & get_impl() {return *reinterpret_cast<T*>(bytes);}

and don't forget to destroy it (but only if you created it):

get_impl().~T();

You may use something like:

template<class T> struct Atom
{
public:
    ~Atom() { if (ptr) ptr->~T(); }

    template <typename...Ts>
    void create(Ts&&... args)
    {
        ptr = new (buffer) T(std::forward<Ts>(args)...);
    }

    T* get() { return ptr; }

private:
    T* ptr = nullptr;
    alignas(T) std::uint8_t buffer[sizeof(T)];
    int index, counter;
    State state;
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top