What is the Right Way to use C++ allocators?

  allocator, c++

C++ allocators (as used by std::vector) are tricky to. I understand that they changed a lot to allow stateful allocators and PMR, leading to some of the cruftiness. My core question is: if allocators are intended to replace new and delete, why do they only provide an API like malloc and free? I understand why, e.g., std::vector needs a malloc interface since it needs to allocate a buffer without calling constructors, but in general, it seems like we are missing these functions:

#include <cassert>
#include <iostream>
#include <memory>

template <typename T, typename Alloc>
void destruct_and_deallocate(Alloc& alloc, T* ptr) {
    assert(ptr);
    using rebound_alloc_t = typename std::allocator_traits<Alloc>::template rebind_alloc<T>;
    rebound_alloc_t rebound_alloc{alloc};
    using traits_t = std::allocator_traits<rebound_alloc_t>;
    traits_t::destroy(rebound_alloc, ptr);
    traits_t::deallocate(rebound_alloc, ptr, 1);
}

template <typename T, typename Alloc, typename... Args>
[[nodiscard]] auto allocate_and_construct(Alloc& alloc, Args&&... args) {
    using rebound_alloc_t = typename std::allocator_traits<Alloc>::template rebind_alloc<T>;
    rebound_alloc_t rebound_alloc{alloc};
    using traits_t = std::allocator_traits<rebound_alloc_t>;
    T* ptr = traits_t::allocate(rebound_alloc, 1);
    try {
        traits_t::construct(rebound_alloc, ptr, std::forward<Args>(args)...);
    }  catch (...) {
        traits_t::deallocate(rebound_alloc, ptr, 1);
        throw;
    }
    auto dtor = [&alloc](T* ptr) { destruct_and_deallocate(alloc, ptr); };
    return std::unique_ptr<T, decltype(dtor)>(ptr, dtor);
}

struct S {
    float x;
    S(float x) : x(x) { std::cout << "S::S()" << std::endl; }
    ~S() { std::cout << "S::~S()" << std::endl; }
};

int main() {
    std::allocator<int> alloc;
    auto ptr = allocate_and_construct<S>(alloc, 42.5f);
    assert(ptr);
    std::cout << ptr->x << std::endl;
}

Output:

S::S()
42.5
S::~S()

https://godbolt.org/z/dGW7hzdc1

Am I missing something? Is this the right way to implement essentially new and delete and make_unique using allocators? If so, is this really not provided by the standard library?

Edit:
I think (but am not sure?) that if T is allocator-aware, traits_t::construct(a, ptr, n) will propagate itself into the created object?

Source: Windows Questions C++

LEAVE A COMMENT