Código fuente de la biblioteca Cstl
Template<class_Ty, class_Ax=Allocator<_Ty>>
Vector de clasificación
Sin embargo, básicamente pocas personas personalizan un asignador. Primero, el asignador predeterminado es suficiente; segundo, realmente no sé cómo usarlo. En términos generales, no necesitamos redefinir un asignador. El método de personalización es principalmente para mejorar el rendimiento de las operaciones relacionadas con la asignación de memoria. El rendimiento de STL es bastante bueno. De hecho, en la plataforma Windows, la implementación subyacente de new se basa en la función malloc del lenguaje C; la familia de funciones Malloc se implementa en base a Windows HeapCreate, HeapAlloc, HeapFree y otras API relacionadas. (Para obtener más detalles, consulte heapinit.c, malloc.c, new.cpp y otras funciones relacionadas en el directorio % VSInstallFolder % \ VC \ CRT \ src \ CRT \ src).
Dejando a un lado los problemas de rendimiento, veamos cómo implementar nuestro propio asignador.
En el documento estándar de C++ 2003, no hay mucha descripción del asignador. Probablemente hay dos ubicaciones principales: 20.1.5 Requisitos del asignador y 20.4.1 Asignador predeterminado. Aunque el contenido no es mucho, nos basta con escribir nuestro propio asignador.
De acuerdo con los requisitos del asignador, debemos proporcionar algunas definiciones de tipo:
1: Plantilla y lttypename T & gt
2: Clase CHxAllocator
3: {
4:Público:
5: // typedefs...
6:typedef T valor _ tipo; p>
7: typedef valor_tipo*puntero;
8: tipo de valor typedef y referencia;
9: typedef valor_tipo constante*puntero_constante;
valor typedef _ tipo const & ampconst _ referencia
11:typedef tamaño _ t tamaño _ tipo
12: typedef ptrdiff _ t diferencia _ tipo; 13:
14: // rebind...
15: Plantilla & lttypename _ other & gt estructura rebind { typedef CHxAllocator & lt_other & gt other };
16: };
Hay una cosa que no es fácil de entender aquí: volver a vincular. La descripción de la revinculación en el estándar C++ es la siguiente:
La plantilla de clase miembro revinculada en la tabla anterior es en realidad una plantilla typedef: si el asignador de nombres está vinculado a SomeAllocator & ltT & gt, entonces p> p>
Allocator::rebind<U>* other es del mismo tipo que SomeAllocator<U>.
¿Qué quieres decir? Se puede utilizar un ejemplo sencillo para ilustrar:
Aprendí estructuras de datos en la escuela, como pilas, listas enlazadas unidireccionales y árboles. Comparemos pilas y listas para ver cuáles son las grandes diferencias. Dejando de lado las diferencias en las estructuras de datos, desde la perspectiva del asignador, podemos encontrar que la pila almacena los elementos en sí, mientras que la lista vinculada en realidad no almacena los elementos en sí directamente. Para mantener una lista, necesitamos al menos un puntero llamado next. Por lo tanto, aunque es una lista de entradas almacenadas, list
A continuación, debemos proporcionar otras interfaces.
Según la descripción del asignador predeterminado, debemos proporcionar la siguiente interfaz:
Dirección de puntero (valor de referencia) constante
Dirección de puntero_constante (valor de referencia_constante)
Devuelve la dirección de val.
La asignación de puntero (size_type cnt, CHxAllocator & ltvoid & gt* const _ pointer pHint = 0) asigna espacio. Similar a malloc. PHint se puede ignorar, principalmente para mejorar el rendimiento de la biblioteca de clases.
Void deallocate(pointer p, size_type n) libera espacio, similar a free.
El número máximo que se puede asignar.
Voidconstruct(pointer p, const_referenceval) llena el espacio señalado por la dirección p con val. Debe utilizar la ubicación nueva para asegurarse de que se llame al constructor.
La destrucción vacía (puntero p) destruye el contenido del bloque de memoria señalado por p, generalmente realizado mostrando el destructor.
Allocator() throws()
Allocator(const_reference) throws()
Plantilla y lttypename _ other & gt allocator(CHxAllocator & lt_Others & gtconst & amp ) throw()
~CHxAllocator() throw()
Varios constructores y destructores
Cómo implementar estas funciones, simplemente copie la implementación en la biblioteca estándar . Si desea utilizar malloc en lugar de C, también puede escribir:
1: asignación de puntero (size_type cnt, CHxAllocator & ltvoid & gt* const _ pointer pHint = 0)
2: {
3: sin referencia _ PARAMETRO(pHint);
4:
5:if(CNT<= 0)
6: {
7: Devuelve 0
8: }
9:
10:void * pMem = nullptr ;
11:if(max_size()<CNT | |(pMem = malloc(CNT * sizeof(value_type)))= = NULL)
12: {
13:lanzar STD::bad _ alloc(0);
14: }
15:
16 :return static_cast<pointer>(pMem) ;
17: }
18:
19: asignación nula (puntero p, tipo_tamaño)
20: {
21: Gratis (p);
22: }
23:
24: construcción vacía (puntero p, valor de referencia constante) p>
25: {
26:::new((void *)p)T(val);
27: }
28 :
29: Destrucción no válida (puntero p)
30: {
31:p- >~T();
32: }
Básicamente, acabamos de implementar nuestro propio asignador. Además, además de estas funciones principales de la interfaz, también es necesario implementar los operadores de comparación == y ! =, pero estas letras devuelven directamente verdadero o falso según la documentación estándar.
Como dije al principio, el objetivo principal de reescribir el asignador es mejorar el rendimiento.
Entonces, ¿cómo se puede mejorar el rendimiento? ¿Utilizar directamente la API de memoria dinámica HeapXXXX de Windows? De hecho, cuando lo use usted mismo, encontrará que la mejora del rendimiento no es obvia. Porque pasar new, luego malloc y finalmente heapaloc no es más que llamar a heapaloc directamente. Cómo implementar un asignador de alto rendimiento requiere la idea de un grupo de memoria. Además, el análisis del código fuente stl de Hou Jie utilizó una idea similar para analizar la asignación implementada por SGI STL.