polymorphic_allocator: when and why should I use i

2019-01-10 16:05发布

问题:

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?

回答1:

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.



回答2:

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.