polymorphic_allocator: when and why should I use it?

skypjack picture skypjack · Jun 24, 2016 · Viewed 19.1k times · Source

Here is the documentation on cppreference, here is the working draft.

I must admit that I didn't understand what's the real purpose of polymorphic_allocator and when/why/how I should use it.
As an example, the pmr::vector has the following signature:

namespace pmr {
    template <class T>
    using vector = std::vector<T, polymorphic_allocator<T>>;
}

What does the polymorphic_allocator offer? What does the std::pmr::vector offer as well in regard of the old-fashioned std::vector? What can I do now that I wasn't able to do till now?
What's the real purpose of that allocator and when should I use it actually?

Answer

davmac picture davmac · Jun 24, 2016

Choice quote from cppreference:

This runtime polymorphism allows objects using polymorphic_allocator to behave as if they used different allocator types at run time despite the identical static allocator type

The issue with "regular" allocators is that they change the type of the container. If you want a vector with a specific allocator, you can make use of the Allocator template parameter:

auto my_vector = std::vector<int,my_allocator>();

The problem now is that this vector is not the same type as a vector with a different allocator. You can't pass it to a function which requires a default-allocator vector, for example, or assign two vectors with a different allocator type to the same variable / pointer, eg:

auto my_vector = std::vector<int,my_allocator>();
auto my_vector2 = std::vector<int,other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error

A polymorphic allocator is a single allocator type with a member that can define the allocator behaviour via dynamic dispatch rather than through the template mechanism. This allows you to have containers which use specific, customised allocation, but which are still of a common type.

The customisation of allocator behavior is done by giving the allocator a std::memory_resource *:

// define allocation behaviour via a custom "memory_resource"
class my_memory_resource : public std::pmr::memory_resource { ... };
my_memory_resource mem_res;
auto my_vector = std::pmr::vector<int>(0, &mem_res);

// define a second memory resource
class other_memory_resource : public std::pmr::memory_resource { ... };
other_memory_resource mem_res_other;
auto my_other_vector = std::pmr::vector<int>(0, &mes_res_other);

auto vec = my_vector; // type is std::pmr::vector<int>
vec = my_other_vector; // this is ok -
      // my_vector and my_other_vector have same type

The main remaining issue, as I see it, is that a std::pmr:: container is still not compatible with the equivalent std:: container using the default allocator. You need to make some decisions at the time you design an interface which works with a container:

  • is it likely that the container passed in may require custom allocation?
  • if so, should I add a template parameter (to allow for arbitrary allocators) or should I mandate the use of a polymorphic allocator?

A template solution allows for any allocator, including a polymorphic allocator, but has other drawbacks (generated code size, compile time, code must be exposed in header file, potential for further "type contamination" which keeps pushing the problem outward). A polymorphic allocator solution on the other hand dictates that a polymorphic allocator must be used. This precludes using std:: containers which use the default allocator, and might have implications for interfacing with legacy code.

Compared to a regular allocator, a polymorphic allocator does have some minor costs, such as the storage overhead of the memory_resource pointer (which is most likely negligible) and the cost of virtual function dispatch for allocations. The main problem, really, is probably lack of compatibility with legacy code which doesn't use polymorphic allocators.