EVAP-2 IIME-3

Librería C++ <memory>

Asignadores de memoria
1 Antecedentes

Para comprender las características y utilidad de los asignadores ("Allocators"), donde afirmábamos que existen versiones de C++ para prácticamente todas las plataformas hardware y Sistema Operativo conocidos. Es significativo que las diferentes combinaciones de máquinas y SOs utilizan distintos modelos de memoria, a pesar de lo cual, C++ utiliza un modelo único cuyos detalles de implementación están encapsulados en el compilador, de forma que el usuario no tiene que preocuparse de ellos. Por ejemplo, la diferencia entre un puntero señalando al elemento enésimo de una matriz y el que señala al primer elemento es siempre un entero n, con independencia del modelo de memoria de la máquina utilizada.
En C++ existen dos mecanismos relacionados con la gestión de memoria: el que se ocupa del manejo de los objetos automáticos y el que se ocupa de la memoria dinámica.  El primero, que maneja la pila, está inextricablemente unido con el propio lenguaje (recordemos que C y C++ son lenguajes orientados a la pila) y ha sido descrito en el apartado. El segundo, que entiende de la asignación ("allocation") de memoria a los objetos persistentes, y su desasignación o liberación ("free") cuando ya no son necesarios, es en cierta forma un "añadido" al propio lenguaje, y de hecho, su interfaz para el programador (como lo utiliza), ha variado a lo largo del tiempo (se incluyó una descripción al respecto al tratar del operador new
Cuando se diseñó la STL, y debido precisamente a los distintos modelos de memoria a los que debería adaptarse, los miembros del Comité ya tenían en mente la previsible dificultad para implementar la enorme cantidad de código que constituía la nueva librería . Con el fin de facilitar su implementación en los distintos compiladores (uno de los objetivos preferentes del Comité de Estandarización C++ es facilitar la portabilidad del lenguaje a las diversas plataformas), los puntos en que se hacía referencia al manejo de memoria se redujeron al mínimo, concentrándolos en una clase que se encargaría de asignar y desasignar memoria para todos los demás algoritmos que lo precisaran. El resultado es que los objetos de esta clase (allocators) permiten desvinculan la STL de los aspectos relativos al modelo de memoria utilizado por cada máquina concreta.  

2 Asignadores  


Habida cuenta que la misión principal de los contenedores es el almacenamiento de objetos, resulta natural que entre sus funciones más importantes, se encuentre la gestión del espacio necesario para albergarlos. Esta gestión no se limita a asignar y liberar el espacio necesario para sus miembros. En ocasiones también debe gestionar espacio para la estructura de índices que mantiene el orden de la secuencia. Por ejemplo, cuando añadimos miembros a un contenedor tipo vector , este se redimensiona automáticamente conforme se van añadiendo nuevos elementos.  Análogamente, cuando añadimos nuevos elementos a un map  este acomodo afecta a los nuevos elementos y a las claves asociadas que constituyen su sistema de índice.

Como se ha dicho, todas las entidades de la STL que precisan de memoria dinámica lo hacen a través de los "servicios" de una clase "asignadora" que es utilizada como argumento (recuerde que las entidades de la STL son plantillas que aceptan distintos parámetros). Debido a que además de portable, la STL es extensible, existe libertad para que el usuario implemente su propio asignador de memoria  aunque la STL proporciona un gestor por defecto, la clase allocator, que implementa la funcionalidad requerida mediante la utilización de los operadores estándar new y delete . En caso de no indicarse otro explícitamente, se utiliza este asignador por defecto, que suele ser suficiente en la mayoría de los casos. El resultado es que el usuario puede despreocuparse de la cuestión, y que los asignadores son ráramente utilizados de forma explícita. Por ejemplo, la definición del contenedor vector es del siguiente tenor:



Desde la perspectiva del usuario de un contenedor estándar, el manejo de memoria es realizado automáticamente mediante el parámetro de moldeo.  Por ejemplo, para definir una lista de enteros myList utilizando el asignador por defecto, puede utilizarse el parámetro <int> y escribir:


En la declaración del contenedor también puede proporcionarse un asignador específico mediante un segundo parámetro de moldeo. Por ejemplo, para utilizar en la lista anterior un asignador propio denominado fastAllocator, que suponemos está pensado para tipos int, y definido en el fichero de cabecera <fastAllocator.h>, utilizaríamos la definición siguiente:



También podría utilizarse el asignador por defecto de forma explícita:



Como puede verse, cuando se instancia una especialización concreta de un contenedor genérico, debe especificarse el tipo de miembro que alojará en el contenedor y el asignador de memoria que utilizara. Posteriormente, cuando se instancie un objeto del tipo myList, se especifica el objeto-asignador que utilizará el contenedor:


Debemos observar que en el diseño actual de la STL, los asignadores son clases genéricas (plantillas), y que una instancia de dicha plantilla asigna memoria para objetos de un tipo específico T. En consecuencia, cuando proporcionamos un allocator específico a un contenedor, su tipo debe coincidir con el de los miembros del contenedor.


A este respecto señalemos que el Estándar establece que condiciones debe cumplir una clase M para que pueda ser considerada un allocator y utilizada por las entidades de la STL que lo necesiten. En concreto establece que debe tener ciertas propiedades cuyos nombres y significados están determinados, así como ciertos métodos cuyos nombres y comportamiento están igualmente especificados. Entre ellos podríamos destacar los siguientes:
ExpresiónValor devueltoComentario
M::pointerPuntero a tipo T.
M::size_typeEntero sin signoUn tipo que puede representar el tamaño del mayor objeto en el modelo de memoria utilizado.
M::difference_typeEntero con signoTipo que puede representar la diferencia entre dos punteros cualesquiera en el modelo de memoria utilizado.
a.allocate(n)
a.allocate(n,u)
M::pointerAsigna memoria para n objetos de tipo T (sin que los objetos sean construidos). En caso de fallo debe lanzar una excepción.
a.deallocate(p,n)Desasigna la memoria para n objetos a partir de la dirección señalada por el puntero p.  La memoria debe haber sido asignada previamente con allocate(), y los objetos contenidos en ella haber sido destruidos previamente.
a1 == a2boolLa clase M debe terner definido el método operator== de forma que devuelvatrue si el almacenamiento asignado desde uno puede ser desasignado desde el otro.
a1 != a2boolEquivalente a: !(a1 == a2)


En el cuadro de condiciones anterior se observa que los asignadores no crean los objetos. Se limitan a asignar memoria al más puro estilo de las funciones de librería clásica calloc() y malloc(). Lo que realmente caracteriza a esta clase son los métodosallocate() y deallocate() que, en cierta forma, representan la contrapartida de las funciones malloc() y free() de la librería clásica.
La STL incluye también algunos algoritmos para manipular la memoria no inicializada.


El funcionamiento del sistema se basa en que cada vez que es creado un contenedor que necesita manejo de memoria, recibe un asignador. De esta forma, cada vez que el contenedor necesita asignar o rehusar memoria no necesita conocer ningún detalle sobre el modelo de memoria 
3 El manejador por defecto


Las definiciones de los asignadores de la STL están en el espacio de nombres std y se encuentran agrupadas en la cabecera <memory>.  La STL proporciona un manejador por defecto denominado allocator.  Esta clase es en realidad una plantilla que puede instanciarse para manejar cualquier tipo. Responde a la siguiente interfaz:de la máquina, ya que utiliza el asignador para estos menesteres.

Por supuesto que la clase satisface las premisas indicadas por el Estándar para ser un allocator  por lo que es un asignador estándar. También se proporciona una instanciación para el tipo void que responde a la siguiente interfaz:


4 Los elementos de memoria

4.1 Los asignadores


4.2 Punteros administrados


Funciones y clases relacionadas con shared_ptr :

4.3 Memoria sin inicializar

Raw iterador de almacenamiento :

Tampones temporales :

Algoritmos especializados :

4.4 Modelo de memoria

No hay comentarios:

Publicar un comentario