2012-09-23 19 views
14

Hay un parámetro de plantilla para contenedores STL para elegir un asignador personalizado. Me tomó un tiempo, pero creo que entiendo cómo funciona. De alguna manera, no es realmente agradable porque el tipo de asignador dado no se usa directamente, sino que se rebota al asignador de otro tipo. Finalmente puedo trabajar con eso.Contenedor STL: Parámetro del asignador de constructor y asignadores de ámbito

Después de leer el API, reconocí que también existe la posibilidad de asignar asignadores como parámetro de constructor. Pero, ¿cómo puedo saber qué tipo de asignador usa el contenedor si vuelve a enlazar internamente el asignador dado desde el parámetro de la plantilla?

Además, leí que C++ 11 ahora usa asignadores de ámbito que permiten reutilizar el asignador de un contenedor para sus contenedores. ¿Cómo difiere aproximadamente la implementación de un contenedor habilitado de asignador de ámbito de uno que no tiene conocimiento de los contenedores delimitados?

Desafortunadamente no he podido encontrar nada que pueda explicar esto. Gracias por las respuestas!

Respuesta

13

Pero, ¿cómo puedo saber qué tipo de asignador utiliza el contenedor, si se vuelve a enlazar internamente el asignador dada desde el parámetro de plantilla?

oferta siempre un Allocator<T> al constructor (donde T es el value_type del recipiente). El contenedor lo convertirá en un Allocator<U> es necesario donde U es una estructura de datos interna del contenedor.El Allocator se requiere para suministrar dichos constructores de conversión, por ejemplo .:

template <class T> class allocator { 
    ... 
    template <class U> allocator(const allocator<U>&); 

Además leí que C++ 11 ahora utiliza asignadores de ámbito que permiten Para reutilizar el asignador de un recipiente para sus recipientes que contienen.

Bueno, para ser más precisos, C++ 11 tiene un adaptador asignador llamada scoped_allocator_adaptor:

template <class OuterAlloc, class... InnerAllocs> 
class scoped_allocator_adaptor : public OuterAlloc 
{ 
    ... 
}; 

De C++ 11:

La plantilla de clase es scoped_allocator_adaptor una plantilla de asignador que especifica el recurso de memoria (el asignador externo) que va a ser utilizado por un contenedor (como hace cualquier otro asignador) y también especifica un recurso interno de asignación que se pasará al constructor de cada elemento dentro del contenedor. Este adaptador está instanciado con uno externo y cero o más tipos internos de asignador. Si se crea una instancia con un solo tipo de asignador , el asignador interno se convierte en scoped_allocator_adaptor, utilizando el mismo recurso del asignador para el contenedor y cada elemento dentro del contenedor y, si los elementos en sí son contenedores, cada uno de sus elementos recursivamente. Si se instancia con más de un asignador, el primer asignador es el asignador externo para el uso del contenedor, el segundo asignador se pasa a los constructores de los elementos del contenedor, y, si los elementos en sí son contenedores, el tercer asignador es pasado a los elementos elementos, y así sucesivamente. Si los contenedores están anidados a una profundidad mayor que el número de asignadores, el último asignador se utiliza repetidamente, como en el caso de un solo asignador, para cualquier recursividad restante. [Nota: scoped_allocator_adaptor se deriva del tipo de asignador externo por lo que puede sustituirse por el asignador externo en la mayoría de las expresiones. - nota final]

lo que sólo recibe el comportamiento asignadores de ámbito si se especifica un scoped_allocator_adaptor como el asignador de su contenedor.

¿Cómo difiere la implementación de un contenedor con asignación de ámbito de uno que no tiene conocimiento de los contenedores de ámbito?

La clave es que el contenedor se ocupa ahora con su asignador a través de una nueva clase llamada allocator_traits lugar de tratar con el asignador directamente. Y el contenedor debe usar allocator_traits para ciertas operaciones como construir y destruir value_type s en el contenedor. El contenedor no debe hablar directamente con el asignador.

Por ejemplo, los asignadores de puede proporcionar un miembro de llama construct que construirá un tipo en una dirección determinada usando los argumentos dados:

template <class T> class Allocator { 
    ... 
    template<class U, class... Args> 
     void construct(U* p, Args&&... args); 
}; 

Si un asignador no proporciona este miembro, allocator_traits proporcionarán una implementación predeterminada. En cualquier caso, el contenedor debe construir todas value_type s utilizando esta función construct, pero usarlo a través allocator_traits, y no usar la allocator directamente:

allocator_traits<allocator_type>::construct(the_allocator, *ugly details*); 

El scoped_allocator_adaptor proporciona encargo construct funciones que allocator_traits a pasar a los que se aprovecha los rasgos uses_allocator y pasa el asignador correcto junto al constructor value_type. El contenedor permanece maravillosamente ignorante de estos detalles. El contenedor solo debe saber que debe construir el value_type utilizando la función allocator_traits construct.

Hay más detalles que el contenedor debe tener que manejar para gestionar correctamente las asignaciones con estado. Aunque estos detalles también se solucionan al hacer que el contenedor no haga suposiciones sino que obtenga todas las propiedades y comportamientos a través del allocator_traits. El contenedor ni siquiera puede suponer que pointer es T*. Más bien, este tipo se encuentra al preguntar allocator_traits de qué se trata.

En resumen, para construir un contenedor C++ 11, estudie en allocator_traits. Y luego obtiene el comportamiento de asignación de alcance de forma gratuita cuando sus clientes usan el scoped_allocator_adaptor.

4

El tipo de asignador utilizado por un contenedor se define por su argumento constructor: es exactamente este tipo el que se espera en los constructores del contenedor. Sin embargo, cualquier asignador debe ser capaz de servir a tipos diferentes a aquel para el que está definido. Por ejemplo, para un std::list<T, A> el asignador esperado es capaz de asignar el objeto T, pero nunca se usará para asignar estos objetos porque el std::list<T, A> realmente necesita asignar nodos. Es decir, el asignador se recuperará para asignar un tipo diferente. Desafortunadamente, esto hace que sea difícil usar un asignador para servir un tipo específico: no se sabe el tipo que el asignador realmente servirá.

Con respecto a los asignadores de ámbito funciona bastante sencillo: el contenedor determina si tiene algún miembro con un constructor tomando un asignador correspondiente. Si este es el caso, volverá a vincular el asignador que usó y pasará este asignador al miembro. Lo que no es sencillo es la lógica que determina si se está utilizando un asignador. Para determinar si un miembro usa un asignador, se utilizan los rasgos std::uses_allocator<T, A>: determina si T tiene un typedef allocator_type anidado que, y si A se pueden convertir a este tipo. Las reglas sobre cómo se construyen los objetos miembro se describen en 20.6.7.2 [allocator.uses.construction].

En la práctica esto significa que los asignadores son útiles para tratar con un grupo usado para un contenedor y sus miembros. En algunos contextos, también puede funcionar razonablemente cuando se asignan objetos de tamaño similar, p. para cualquiera de los contenedores basados ​​en nodos, para mantener un conjunto de objetos del mismo tamaño. Sin embargo, no es necesario aclarar el patrón utilizado con el asignador si están, por ejemplo, para los nodos o algunas cadenas contenidas en. Además, dado que el uso de diferentes políticas de asignación cambiaría el tipo, parece más razonable quedarse con la asignación predeterminada o utilizar un tipo de asignador que sea un proxy para un asignador polimórfico que realmente defina la política de asignación. Por supuesto, en el momento en que tenga asignadores con estado, puede tener objetos con diferentes asignadores y p. swap() ing them podría no funcionar.

Cuestiones relacionadas