What's the right way to partially initialize a struct?

267 views Asked by At

For a user-defined allocator, the relation between the allocated-units must be constructed at the beginning, while the memory space for elements should be left uninitialized.

A simple demo:

template <typename T>
struct Node {
    T _item;
    Node* _prev;
    Node* _next;
};

template <typename T, typename ParentAllocator>
class MyAllocator {
    using Allocator = std::allocator_traits<ParentAllocator>::rebind_alloc<Node<T>>;
    Allocator _allocator;
    Node<T> _header;
    /* ... */

public:
    MyAllocator()
        : _allocator()
    {
        _header._prev = &_header;
        _header._next = &_header;

        // leaving `_item` uninitialized 
    }

    T* allocate(/*one*/) {
        auto* newNode = _allocator.allocate(1);
        newNode->_prev = &_header;
        newNode->_next = _header._next;

        // leaving `_item` uninitialized 

        /* ... */
        return &(newNode->_item);
    }
};

Node is not initialized, instead direct initialization for its members, though not for all.

My questions are:

  1. Are _header and _next really partially initialized as expectations, even if the default constructor of T (both normal and explicit one) were deleted.
  2. Have I implemented it properly?
  3. If not, what's the right way?
1

There are 1 answers

4
John Zwinck On BEST ANSWER

You need to modify Node to make it default constructible, and you don't want to default construct T even if it has a default constructor. So you can replace T _item with:

std::aligned_storage<sizeof(T), alignof(T)> _item;

Or in C++23 because std::aligned_storage is deprecated:

alignas(T) std::byte _item[sizeof(T)];

That will give you the storage space you need, with appropriate alignment, and then you'll use placement new to construct T in that storage. You will also need to explicitly invoke ~T() before or during destruction of each Node.

Demo showing the basics, certainly not complete or tested: https://godbolt.org/z/bGaKWb3de