martes 24 de marzo de 2009
Nocturnal Initiative: Smart Pointer (II)
Ya ha pasado mucho tiempo desde la última entrada, y ya no te quiero ni contar desde la entrada que precede a esta. Llevo sin escribir en el blog por mezcla de mi dejadez aderezado con ciertos cambios en mi vida laboral. Así que retomo la escritura para completar esta mini-serie iniciada ya hace casi un año, algo que no tiene excusa alguna porque lo que quedaba por explicar era muy sencillo.
Recapitulemos, en nocturnal teníamos un interface IRefCount que definía tres operaciones para contar las referencias a un objeto:
- IncrRefCount() Incrementa en uno el contador de referencias.
- DecrRefCount() Decrementa en uno el contador de referencias.
- GetRefCount() Devuelve el número de referencias del objeto.
A su vez teníamos disponible las clases RefCountBase y RefCountAggregator, que implementan dicho interface.
Pues ahora hablaremos de la clase SmartPtr<T> una clase que tiene como fin comportarse exactamente igual que un puntero, pero gestionando automáticamente el ciclo de vida de un objeto según el número de referencias que tenga. Esto significa que, dada una instancia de un objeto en memoria, este se borrará automáticamente cuando ningún otro SmartPtr lo referencie.
Todo esto funciona siempre que T sea un tipo que implementa IRefCount, por tanto utilizará los tres métodos definidos en dicho interface para gestionar las cuenta de las referencias. Si no es así el código ni compilará. Y aunque no es obligatorio para que el código compile, si que es necesario para que todo funcione que sólo utilizaremos un tipo de puntero SmartPtr para gestionar los objetos: si mezclamos punteros “tradicionales” con smart pointers armaremos un cisco épico con toda probabilidad.
Por supuesto que implemente IRefCount es sólo parte del trabajo, ya que un interface por si sólo no incluye el código necesario para gestionar las referencias. Por ello, y suponiendo que no queremos reinventar la rueda, lo mejor será que cualquier objeto que pretendamos utilizar con un SmartPtr derive de RefCountBase, con lo que en la práctica se convertirá en la clase base de toda nuestra jerarquía de objetos.
Pero ¿cómo funciona SmartPtr internamente? Pues haciendo uso tanto de conversiones implícitas en los constructores como de sobrecarga de operadores para simular el comportamiento de un puntero a la par que actualizas la cuenta de referencias.
El contador de referencias se inicializa a 1 al asignar por primera vez un puntero válido a un SmartPtr, y se va actualizando con cada operación de asignación –decrementando la referencia del SmartPtr sobreescrito e incrementando la del asignado, o cuando el objeto SmartPtr es destruido, momento en el que decrementamos en uno el contador de referencias.
Menudo lío, veamos algo de código:
Empezamos con una clase sencilla de prueba
//Clase simple que deriva de BaseRefCount para
//implementar el contador de referencias
class MyClass : BaseRefCount {};Creamos un par de SmartPtr que apuntan a instancias diferentes de MyClass
//ptr2MyClass1: Contador de referencia a 1 //apunta a la instancia 1 de MyClass SmartPtr< MyClass > ptr2MyClass1 = new MyClass(); //ptr2MyClass2: Contador de referencia a 1 //apunta a la instancia 2 de MyClass SmartPtr< MyClass > ptr2MyClass2 = new MyClass();
Ahora mismo ptr2MyClass1 != ptr2MyClass2 ya que apuntan a instancias de objetos distintas.
Sin embargo con una "simple" asignación vamos a ver que ocurren varias cosas:
ptr2MyClass2 = ptr2MyClass1;
ptr2MyClass2 está siendo sobreescrito, con lo que decrementamos su contador de referencias. Como era 1, ahora pasa a ser 0, por tanto como ningún puntero referencia la instancia, hemos de borrar dicha instancia, y así lo hacemos, o mejor dicho, así se hace automáticamente. Por otro lado al asignarle el puntero ptr2MyClass1, lo que estamos estamos diciendo es que ptr2MyClass2 apunte a la misma instancia que ptr2MyClass1, que es la instancia 1 Eso implica que ahora tenemos DOS referencias a la misma instancia, por lo que incrementamos el contador de referencia que pasa a ser 2.
Ahora mismo ptr2MyClass1 == ptr2MyClass2: ambos apuntan a la misma instancia en memoria. Vamos a asignar un puntero nulo:
ptr2MyClass2 = 0;
Como estamos asignando una referencia nula, lo que haremos será decrementar el contador de referencias, con lo que pasa a ser 1.
Por último, imaginemos que ptr2MyClass1, que es el único SmartPtr que apunta a la instancia 1 sale del ámbito actual en el que ha sido declarado, algo como esto:
{
SmartPtr< MyClass > ptr2MyClass1 = new MyClass();
}
//Aquí salimos de ámbito, por lo que llamamos al
//destructor definido para ptr2MyClass1Pues bien, como al salir de ámbito su destructor es invocado el contador de referencias es decrementado de nuevo. Como éste era 1, pasa a ser 0, y por tanto la instancia es borrada automáticamente.
Como vemos estamos trabajando con punteros sin necesidad de gestionar la memoria y sin crear fugas de memoria.
Pero mucho ojo, ¡porque esto no es válido para semánticas de punteros como arrays!
Por ejemplo este código compila, pero genera fugas de memoria:
SmartPtr< MyClass >* myClassArray = new SmartPtr< MyClass > [2]; myClassArray[0] = new MyClass(); myClassArray[1] = new MyClass();
Eso es debido a que al destruir el puntero myClassArray, no iteramos por cada uno de sus elementos para destruir los objetos que almacena. En estos casos lo más cómodo es utilizar la clase vector de stl, que al destruir un vector si llama al destructor de cada uno de los elementos que almacena:
vector< SmartPtr> myClassArray = vector< SmartPtr< MyClass > > (2); myClassArray[0] = new MyClass(); myClassArray[1] = new MyClass();
Y ya tenemos arrays que almacenan punteros sin fugas de memoria y auto-gestionados 
Como nota final, en la última versión del NocturnalFramework, que ya salió hace algún tiempo la clases necesarias para utilizar SmartPtr se encuentran en el directorio Common/Memory. Si queréis trastear con ella tened en cuenta que hay referencias a ficheros que se encuentran dentro del directorio Common, por lo que lo más cómodo es copiar la carpeta Common completa a vuestro proyecto.





0 Comentarios:
Publicar un comentario en la entrada