que hace definicion c++ memory-management c++11 containers allocator

c++ - hace - allocator definicion



¿Cuál es el propósito de std:: scoped_allocator_adaptor? (2)

Si desea un contenedor de cadenas y desea usar el mismo asignador para el contenedor y sus elementos (para que todos estén asignados en el mismo ámbito, como lo describe TemplateRex), puede hacerlo manualmente:

template<typename T> using Allocator = SomeFancyAllocator<T>; using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>; using Vector = std::vector<String, Allocator<String>>; Allocator<String> as( some_memory_resource ); Allocator<char> ac(as); Vector v(as); v.push_back( String("hello", ac) ); v.push_back( String("world", ac) );

Sin embargo, esto es incómodo y propenso a errores, porque es demasiado fácil insertar una cadena que no usa el mismo asignador:

v.push_back( String("oops, not using same memory resource") );

El propósito de std::scoped_allocator_adaptor es propagar automáticamente un asignador a los objetos que construye si admiten la construcción con un asignador . Así que el código anterior se convertiría en:

template<typename T> using Allocator = SomeFancyAllocator<T>; using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>; using Vector = std::vector<String, std::scoped_allocator_adaptor<Allocator<String>>>; /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ Allocator<String> as( some_memory_resource ); Allocator<char> ac(as); Vector v(as); v.push_back( String("hello") ); // no allocator argument needed! v.push_back( String("world") ); // no allocator argument needed!

Ahora, el asignador del vector se utiliza automáticamente para construir sus elementos, aunque los objetos que se insertan, String("hello") y String("world") , no se construyen con el mismo asignador. Dado que basic_string se puede construir implícitamente a partir de const char* las dos últimas líneas se pueden simplificar aún más:

v.push_back( "hello" ); v.push_back( "world" );

Esto es mucho más simple, más fácil de leer y menos propenso a errores, gracias a que scoped_allocator_adaptor construye los elementos con el asignador del vector automáticamente.

Cuando el vector le pide a su asignador que construya un elemento como una copia de obj , llama:

std::allocator_traits<allocator_type>::construct( get_allocator(), void_ptr, obj );

Normalmente, el miembro construct() del asignador llamaría algo como:

::new (void_ptr) value_type(obj);

Pero si el allocator_type es scoped_allocator_adaptor<A> entonces utiliza la metaprogramación de la plantilla para detectar si value_type puede construirse con un asignador del tipo adaptado. Si value_type no usa asignadores en sus constructores, entonces el adaptador sí:

std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, obj);

Y eso llamará al miembro construct() del asignador anidado, que usa algo como la ubicación nueva, como se muestra arriba. Pero si el objeto no admite tomar un asignador en su constructor, entonces el scoped_allocator_adaptor<A>::construct() hace:

std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, obj, inner_allocator());

o:

std::allocator_traits<outer_allocator_type>::construct(outer_allocator(), void_ptr, std::allocator_arg, inner_allocator(), obj);

es decir, el adaptador pasa argumentos adicionales cuando llama a construct() en su asignador anidado, de modo que el objeto se construirá con el asignador. El inner_allocator_type es otra especialización de scoped_allocator_adaptor , por lo que si el tipo de elemento es también un contenedor, utiliza el mismo protocolo para construir sus elementos, y el asignador puede pasar a cada elemento, incluso cuando tiene contenedores de contenedores de contenedores, etc.

Entonces, el propósito del adaptador es envolver un asignador existente y realizar toda la metaprogramación y manipulación de los argumentos del constructor para propagar los asignadores de un contenedor a sus hijos.

En el estándar C ++ 11 tenemos std::scoped_allocator_adaptor en la biblioteca de administración de memoria dinámica. ¿Cuáles son los casos de uso más importantes de esta clase?


Supongamos que tiene un Asignador de arena con estado con un constructor Alloc(Arena&) que permite un rendimiento especial para su aplicación, y diga que utiliza una jerarquía anidada de contenedores como este:

using InnerCont = std::vector<int, Alloc<int>>; using OuterCont = std::vector<InnerCont, std::scoped_allocator_adaptor<Alloc<InnerCont>>>;

En este caso, el uso de scoped_allocator_adaptor te permitirá propagar el objeto arena utilizado para inicializar tu asignador desde el contenedor externo hacia el interior de esta manera:

auto my_cont = OuterCont{std::scoped_allocator_adaptor(Alloc<InnerCont>{my_arena})};

Esto permite una mayor localidad de datos y le permite asignar previamente un gran espacio de memoria my_arena para toda su jerarquía de contenedores, en lugar de solo hacer que my_arena esté disponible para el contenedor externo, y requiere un bucle en todos los contenedores internos con otro escenario para cada elemento en ese nivel .

La plantilla de clase es en realidad una plantilla variada que le brinda un control detallado sobre qué tipo de asignador se debe usar en cada tipo de jerarquía de contenedores. Es de suponer que esto proporciona a las estructuras de datos complicadas un mejor rendimiento (debo confesar que no parece que haya diferentes asignadores en diferentes niveles en acción en cualquier lugar, pero tal vez los grandes centros de datos con multimillonarios usuarios tengan un caso de uso aquí).