polymorphic_allocator: when and why should I use it?
polymorphic_allocator
is to a custom allocator as std::function
is to a direct function call.
It simply lets you use an allocator with your container without having to decide, at the point of declaration, which one. So if you have a situation where more than one allocator would be appropriate, you can use polymorphic_allocator
.
Maybe you want to hide which allocator is used to simplify your interface, or maybe you want to be able to swap it out for different runtime cases.
First you need code that needs an allocator, then you need to want to be able to swap which one is used, before considering pmr vector.
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.
One drawback of polymorphic allocators is that polymorphic_allocator<T>::pointer
is always just T*
. That means you can't use them with fancy pointers. If you want to do something like place elements of a vector
in shared memory and access them through boost::interprocess::offset_ptr
s, you need to use a regular old non-polymorphic allocator for that.
So, although polymorphic allocators let you vary allocation behavior without changing a container's static type, they limit what an allocation is.