25/1/09

Smart Pointers

En C++, uno de los problemas más frecuentes, son los memory leaks ocasionados por no liberar la memoria dinámica. A diferencia de otros lenguajes de programación (como Java) C++ no tiene un Garbage Collector que se ocupe de liberar las posiciones de memoria no utilizadas. Pero tiene una técnica muy eficiente llamada Smart Pointers, que evita a los programadores la tediosa tarea de liberar la memoria, y por ende mucho tiempo de debugging.

La memoria se encuentra dividida en:
  • El stack (o pila). Que es la memoria asignada por el sistema operativo al programa ni bien se ejecuta. Su tamaño es conocido en tiempo de compilación y es estático, no puede cambiarse en tiempo de ejecución.
  • El heap (o montículo). Que es la memoria asignada dinámicamente al programa por el sistema operativo en tiempo de ejecución. Sirve para almacenar objetos cuya existencia no es conocida en tiempo de compilación. Su tamaño puede variar a medida que el programa solicite/libere memoria.

La memoria del heap (la memoria dinámica) sirve para solicitar memoria en tiempo de ejecución. Y se puede solicitar en C++ a través de los punteros.

Esta memoria, debe ser siempre liberada cuando deja de utilizarse. Es decir, cuando creamos un objeto en el heap, estamos ocupando lugar en la memoria, si después no lo liberamos, se va a provocar un leak de memoria.

Claro que con la potencia de las CPUs hoy en día, un leak de memoria no es muy significativo… Pero si esto ocurre con más objetos y de mayor tamaño, los leaks de memoria van a hacer al programa cada vez más lento hasta terminar abruptamente.

Para solucionar esto, en lenguajes como Java, existe el Garbage Collector, que es un proceso en background que va recorriendo la memoria y buscando posiciones de memoria no asociadas con ninguna variable/constante del programa, para finalmente liberarlas. Obviamente es más ineficiente que liberar la memoria manualmente, ya que tiene que haber un proceso recorriendo la memoria. Y además trae muchas otras complicaciones, porque la memoria no es liberada inmediatamente que un objeto sale de su scope, sino que cuando el Garbage Collector lo decida.

Bueno, en C++, para evitar que los programadores tengan que liberar la memoria solicitada, existen los Smart Pointers (o punteros inteligentes) que son un patrón de diseño que sirve para que los punteros se liberen automáticamente sin que tengamos que hacerlo nosotros.

Los Smart Pointers se basan en el idioma RAII (pueden ver más información sobre RAII en el artículo de RAII). Básicamente se trata de encapsular un puntero en un stack object, actuando este como un “wrapper” para el puntero. Implementando su constructor para que cree el puntero y su destructor para que lo destruya. Además implementa algunos operadores como * y -> para simular un puntero común y corriente.

Como ya sabemos, los stack objects, son objetos que se crean en el stack, y por ende son destruidos automáticamente al salir de su scope (al contrario de los que se crean en el heap que deben ser destruidos por el programador). Bueno, los Smart Pointers se aprovechan de esto, para encapsular un puntero dentro de un stack object. De esta manera cuando el stack object sea eliminado, también va a ser eliminado el puntero que tiene asociado.

Veamos una implementación de Smart Pointers bien sencilla:

  1. template <class T>
  2. class SmartPointer {
  3. private:
  4. T *pointer;
  5. public:
  6. SmartPointer(T *p) {
  7. pointer = p;
  8. }
  9. ~SmartPointer() {
  10. delete pointer;
  11. }
  12. T * operator ->() {
  13. return pointer;
  14. }
  15. T & operator *() {
  16. return *pointer;
  17. }
  18. };

Por otra parte, tenemos la clase Persona:
  1. class Persona {
  2. private:
  3. char nombre[32];
  4. public:
  5. Persona(char *n) {
  6. strcpy(nombre, n);
  7. cout < < "Mi nombre es " <<>
  8. }
  9. void gritar(char *grito) {
  10. cout << class="string">"!!!!!!!!!!!!" <<>
  11. }
  12. ~Persona() {
  13. cout << "Me mueroooooooooooo" <<>
  14. }
  15. };
Y ahora, vamos a crear el Smart Pointer para la clase Persona:
  1. int main() {
  2. SmartPointer persona(new Persona("pepe"));
  3. persona->gritar("aggghhhhhhhhhhh");
  4. return 0;
  5. }
Como podrán ver, creamos un objeto Persona en el heap (un puntero) y después utilizamos uno de sus métodos. Pero en ningún momento liberamos el objeto con delete. Sin embargo, si ejecutan el código, verán que el objeto se destruye sólo gracias al Smart Pointer:
Esto se debe a que, como dijimos antes, utilizamos a un stack object (SmartPointer) como “wrapper” para el puntero Persona. De esta manera, cuando el stack object salga de su scope y se destruya, va a destruir también al puntero que tiene asociado.

En la STD viene incluido un template para Smart Pointers llamado auto_ptr. Por otro lado, en Boost, hay otras implementaciones más poderosas. Que se dividen en tres tipos:

Scoped Pointers: Prácticamente lo mismo que auto_ptr de la STD, pero implementa la clase noncopyable para que el objeto no pueda ser copiado.

Shared Pointers: Pueden ser copiados, y tienen un contador del número de copias que tienen. Se destruyen sólo cuando el contador llega a cero (no queda ninguna copia).

Intrusive Pointers: Parecido a los Shared Pointers, sólo que el contador lo tiene el objeto que encapsula, y no el Smart Pointer.

Los Smart Pointers son muy poderosos, y pueden ser tan útiles como peligrosos si no se utilizan correctamente. Es recomendable utilizar las implementaciones de Boost, que están bien implementadas y son thread-safe. Aunque también hay muchas implementaciones libres por ahí sueltas muy buenas también.

Extraído de http://sherekan.com.ar

No hay comentarios.: