polymorphic_allocator: when and why should I use it?
Asked Answered
B

3

177

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?

Bagpipe answered 24/6, 2016 at 9:54 Comment(3)
They try to overcome some problems allocator<T> inherently has. So you'll see value in it if you use allocators frequently.Find
Relevant paper.Find
This paper explains the ideas behind polymorphic_allocator in great detail.Storfer
B
173

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.

Bushcraft answered 24/6, 2016 at 10:29 Comment(7)
So, is binary layout for std::pmr:: classes very likely to be different?Patty
@EuriPinhollow you can't reinterpret_cast between a std::vector<X> and std::pmr::vector<X>, if that's what you're asking.Bushcraft
Basically: yes.Patty
For simple cases in which the memory resource doesn't depend on a runtime variable, a good compiler will devirtualize and you end up with a polymorphic allocator with no extra cost (except for storing the pointer which is really not a problem). I thought it was worth mentioning.Ebberta
Your incompatibility section; can you be clear if/if not std::pmr::vector<int> foo; std::vector<int> bar; foo=bar; is legal?Dipstick
@Yakk-AdamNevraumont "a std::pmr:: container is still not compatible with the equivalent std:: container using the default allocator". There is no assignment operator defined from one to the other, either. When in doubt, try it out: godbolt.org/z/Q5BKev (code is not exactly as above because gcc/clang have the polymorphic allocation classes in an "experimental" namespace).Bushcraft
@Bushcraft Ah, so there isn't a template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& ) constructor. I was uncertain, and didn't know where to find a compiler that had TS-compliant pmr.Dipstick
D
47

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.

Dipstick answered 24/6, 2016 at 10:18 Comment(0)
I
16

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_ptrs, 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.

Irregularity answered 22/3, 2019 at 7:7 Comment(3)
This is a key point and a big bummer. Arthur O'Dwyer's Towards meaningful fancy pointers paper explores the territory, as does his book "Mastering the c++17 STL"Mucronate
can you give a real world use case of using polymorphic allocator ?Lustrous
See also #45133046 . This is an example of creating a templatised version of memory_resource in order to support polymophic_allocator with fancy pointers.Dardan

© 2022 - 2024 — McMap. All rights reserved.