c++memory-managementc++17c++pmr

What's the purpose of std::pmr::polymorphic_allocator?


I'm looking at this example in C++ Reference:

std::array<std::byte, total_nodes * 32> buffer; // enough to fit in all nodes
std::pmr::monotonic_buffer_resource mbr{buffer.data(), buffer.size()};
std::pmr::polymorphic_allocator<int> pa{&mbr};
std::pmr::list<int> list{pa};
for (int i{}; i != total_nodes; ++i)
    list.push_back(i);

As I understand it, mbr is the one that actually allocates memory and returns void*.

pa is just some kind of type adapter, and returns int*. However, list<int> nodes aren't actually ints. They contain pointers also. So list.push_back() would require pointer casts anyway.

Is there a reason why std::pmr::polymorphic_allocator exists? Why don't std::pmr containers accept std::pmr::memory_resource objects directly?


Solution

  • To understand what a polymorphic allocator does, you need to understand what you had to do without a polymorphic allocator.

    Normal Allocator

    If you want an std::list that allocates from your custom buffer. The standard way is to pass it an allocator in its template arguments std::list<int, MyAllocator>, this allocator can contain a pointer to your buffer (or it can contain the buffer itself, but not recommended).

    You cannot use this new list to call the following function

    void foo(std::list<int>& l);
    

    because the allocator is embedded into the type system, your list is of a different type.


    Polymorphic Allocator

    Then comes the polymorphic allocator, it contains a pointer to a polymorphic object, an abstract memory_resource, now you can have the following function.

    void foo(std::pmr::list<int>& l)
    

    this function can accept any list that uses a polymorphic allocator, regardless of what the pointed-to memory resource is, it can be the default one, or a monotonic buffer, or a global buffer, or a memory pool, or a disk-mapped buffer, or shared-memory, it is any resource that inherits from the abstract memory_resource.


    the downside of a polymorphic allocator is obviously the use of virtual functions on every allocation and deallocation, which is why you would write your own allocator over using the polymorphic allocator if you need every small drop of performance.

    syntactically, the lack of a constructor that accepts the resource directly is not really a problem.

    std::pmr::monotonic_buffer_resource  b{};
    std::pmr::list<int> a{&b};
    

    the standard library doesn't provide a constructor that accepts b directly, but &b is not very bad (the compiler does an implicit conversion for you).