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:
_header
and _next
really partially initialized as expectations, even if the default constructor of T
(both normal and explicit
one) were deleted.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