polymorphic_allocator: ¿cuándo y por qué debería usarlo?

122

Aquí está la documentación sobre cppreference , aquí está el borrador de trabajo.

Debo admitir que no entendí cuál es el propósito real polymorphic_allocatory cuándo / por qué / cómo debería usarlo.
Como ejemplo, pmr::vectortiene la siguiente firma:

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

¿Qué ofrece la polymorphic_allocatoroferta? ¿Qué std::pmr::vectorofrece también en lo que respecta a los anticuados std::vector? ¿Qué puedo hacer ahora que no pude hacer hasta ahora?
¿Cuál es el propósito real de ese asignador y cuándo debería usarlo realmente?

skypjack
fuente
1
Intentan superar algunos problemas que allocator<T>inherentemente tiene. Por lo tanto, verá valor en él si usa asignadores con frecuencia.
edmz
2
Papel relevante .
edmz

Respuestas:

103

Cita de elección de cppreference:

Este polimorfismo en tiempo de ejecución permite que los objetos que usan polymorphic_allocator se comporten como si usaran diferentes tipos de asignador en tiempo de ejecución a pesar del tipo de asignador estático idéntico

El problema con los asignadores "regulares" es que cambian el tipo de contenedor. Si desea una vectorcon un asignador específico, puede hacer uso del Allocatorparámetro de plantilla:

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

El problema ahora es que este vector no es del mismo tipo que un vector con un asignador diferente. No puede pasarlo a una función que requiera un vector de asignador predeterminado, por ejemplo, o asignar dos vectores con un tipo de asignador diferente a la misma variable / puntero, por ejemplo:

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

Un asignador polimórfico es un tipo de asignador único con un miembro que puede definir el comportamiento del asignador a través del envío dinámico en lugar de a través del mecanismo de plantilla. Esto le permite tener contenedores que utilizan una asignación específica y personalizada, pero que aún son de un tipo común.

La personalización del comportamiento del asignador se realiza dándole al asignador un 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

El principal problema restante, a mi modo de ver, es que un std::pmr::contenedor todavía no es compatible con el std::contenedor equivalente que usa el asignador predeterminado. Necesita tomar algunas decisiones al momento de diseñar una interfaz que funcione con un contenedor:

  • ¿Es probable que el contenedor transferido requiera una asignación personalizada?
  • Si es así, ¿debo agregar un parámetro de plantilla (para permitir asignadores arbitrarios) o debo exigir el uso de un asignador polimórfico?

Una solución de plantilla permite cualquier asignador, incluido un asignador polimórfico, pero tiene otros inconvenientes (tamaño del código generado, tiempo de compilación, el código debe exponerse en el archivo de encabezado, potencial para una mayor "contaminación de tipo" que sigue empujando el problema hacia afuera). Una solución de asignador polimórfico, por otro lado, dicta que se debe utilizar un asignador polimórfico . Esto excluye el uso de std::contenedores que usan el asignador predeterminado y podría tener implicaciones para interactuar con el código heredado.

En comparación con un asignador normal, un asignador polimórfico tiene algunos costos menores, como la sobrecarga de almacenamiento del puntero memory_resource (que probablemente sea insignificante) y el costo del envío de funciones virtuales para asignaciones. El principal problema, en realidad, es probablemente la falta de compatibilidad con el código heredado que no usa asignadores polimórficos.

davmac
fuente
2
Entonces, ¿es std::pmr::muy probable que el diseño binario de las clases sea diferente?
Euri Pinhollow
12
@EuriPinhollow no puedes reinterpret_castentre un std::vector<X>y std::pmr::vector<X>, si eso es lo que estás preguntando.
davmac
4
Para casos simples en los que el recurso de memoria no depende de una variable de tiempo de ejecución, un buen compilador desvirtualizará y terminará con un asignador polimórfico sin costo adicional (excepto para almacenar el puntero que realmente no es un problema). Pensé que valía la pena mencionarlo.
DeiDei
1
@ Yakk-AdamNevraumont "un std::pmr::contenedor todavía no es compatible con el std::contenedor equivalente que usa el asignador predeterminado" . Tampoco hay un operador de asignación definido de uno a otro. En caso de duda, pruébelo: godbolt.org/z/Q5BKev (el código no es exactamente como el anterior porque gcc / clang tienen las clases de asignación polimórfica en un espacio de nombres "experimental").
davmac
1
@davmac Ah, entonces no hay un template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )constructor. No estaba seguro y no sabía dónde encontrar un compilador que tuviera pmr compatible con TS.
Yakk - Adam Nevraumont
33

polymorphic_allocatores a un asignador personalizado como std::functiona una llamada de función directa.

Simplemente le permite usar un asignador con su contenedor sin tener que decidir, en el punto de declaración, cuál. Entonces, si tiene una situación en la que sería apropiado más de un asignador, puede usar polymorphic_allocator.

Tal vez desee ocultar qué asignador se usa para simplificar su interfaz, o tal vez desee poder cambiarlo por diferentes casos de tiempo de ejecución.

Primero necesita un código que necesita un asignador, luego necesita poder intercambiar cuál se usa, antes de considerar el vector pmr.

Yakk - Adam Nevraumont
fuente
7

Un inconveniente de los asignadores polimórficos es que polymorphic_allocator<T>::pointersiempre es justo T*. Eso significa que no puede usarlos con punteros elegantes . Si desea hacer algo como colocar elementos de a vectoren la memoria compartida y acceder a ellos a través de boost::interprocess::offset_ptrs , debe usar un asignador no polimórfico antiguo normal para eso.

Por lo tanto, aunque los asignadores polimórficos le permiten variar el comportamiento de asignación sin cambiar el tipo estático de un contenedor, limitan lo que es una asignación .

Maxpm
fuente
2
Este es un punto clave y un gran fastidio. De Arthur O'Dwyer Hacia punteros de lujo significativas trabajo explora el territorio, al igual que su libro "Mastering the c ++ 17 STL"
sehe
¿Puede dar un caso de uso del mundo real de usar un asignador polimórfico?
darune