0

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?
Nimrod
  • 2,908
  • 9
  • 20
zjyhjqs
  • 609
  • 4
  • 11
  • @JohnZwinck Just a simple demo as I said. Generally the element of `MyAllocator` is a chunk (which contains a buffer for reducing the pressure of allocation and deallocation). `new` operator will evoke the constructor, but I just need to allocate memory space. – zjyhjqs May 01 '22 at 02:17
  • @JohnZwinck `_header` is a node to implement a circular linked-list, as `std::list` does. – zjyhjqs May 01 '22 at 02:21

1 Answers1

1

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

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 1
    Note that `aligned_storage` is deprecated in C++23. – 康桓瑋 May 01 '22 at 02:34
  • 1
    Re: `aligned_storage` being deprecated, see [Why is std::aligned_storage to be deprecated in C++23 and what to use instead?](https://stackoverflow.com/q/71828288/11082165) for rational and alternatives, and [Why does the use of `std::aligned_storage` allegedly cause UB due to it failing to "provide storage"?](https://stackoverflow.com/q/71834788/11082165) more on the current UB issues with it. – Brian61354270 May 01 '22 at 02:41
  • @康桓瑋 Wow that's crazy, thanks for pointing it out. I've added an alternative using `std::byte`. – John Zwinck May 01 '22 at 07:26
  • 1
    `aligned_storage` is just an empty struct. It should be its nested type `type` or the helper struct `aligned_storage_t`. – zjyhjqs May 02 '22 at 03:27