sábado, 29 de mayo de 2010

Shared Pointers de C++0x

En C usted puede hacer esto:
#include <stdlib.h>

struct Persona { };

int main()
{
  Persona* a = malloc(sizeof(Persona));
  free(a);
  return 0;
}

Un ejemplo equivalente en C++:
class Persona { };

int main()
{
  Persona* a = new Persona();
  delete a;
  return 0;
}

Por cada malloc existe un free (al menos que use reallocs), y por cada new existe un delete (y por cada new[] un delete[]).

¿Existe una forma por la cual C++ se "entere" que ya no quiero usar un puntero? La respuesta es: No, no existe. C++ no tiene un garbage collector. Pero existen clases que pueden ayudarnos, como el viejo y tan poco querido auto_ptr, o los mejorados unique_ptr, shared_ptr y weak_ptr del nuevo estándar de C++0x (o el TR1).

Un shared pointer es una clase que se encarga de guardar un puntero a un objeto (o tipo de dato), y cuenta la cantidad de referencias que se están haciendo a dicho objeto (es decir, la cantidad de shared pointers que apuntan al mismo objeto). El último shared pointer que se destruya (cuando las referencias llegan a cero), será el encargado de borrar el objeto apuntado (mediante un simple delete).

Un ejemplo:
#include <iostream>
#include <memory>  // Aquí debería estar shared_ptr<> (GCC 4.4)

using namespace std;

class Persona {
  int n;

public:
  Persona(int n) : n(n) {
    cout << "Nace la persona " << n << "\n";
  }

  ~Persona() { 
    cout << "Muere la persona " << n << "\n";
  }

  static shared_ptr<Persona> Crear(int n) { 
    return shared_ptr<Persona>(new Persona(n));
  }
};

int main()
{
  shared_ptr<Persona> a(new Persona(1));
  shared_ptr<Persona> b = Persona::Crear(2);
  shared_ptr<Persona> c;

  cout << "--- Aquí ambas personas existen ---\n";

  c = a; // Aquí c apunta a la persona 1
  b = c; // Ahora b apuntará a la persona 1 (la persona 2 muere porque
         // ya no existen referencias a ella)

  cout << "--- Aquí la persona 2 ya no existe ---\n";
  
  return 0;
}        // Aquí muere la persona 1 (a, b, c apuntaban a ella)
La salida del anterior programa es esta:
Nace la persona 1
Nace la persona 2
--- Aquí ambas personas existen ---
Muere la persona 2
--- Aquí la persona 2 ya no existe ---
Muere la persona 1
Como puede ver, en el anterior programa se llama sólo dos veces a "new Persona" y dos veces al destructor ~Persona. No debemos preocuparnos por usar "delete", el shared_ptr<> hace todo por nosotros.

¿Cómo hago para que la clase shared_ptr funcione en VS2008 SP1 Express? Debe definir _HAS_TR1 antes de incluir el archivo <memory>. Ejemplo:
#ifdef _MSC_VER      // Si estamos usando el compilador de Microsoft
  #define _HAS_TR1 1 // Esto hará que se incluyan las clases del TR1 (std::tr1)
#endif

#include <iostream>
#include <memory>

using namespace std;
#ifdef _MSC_VER
  using namespace std::tr1; // para tener shared_ptr<> disponible
#endif

// Resto del ejemplo...