Punteros por ámbito (scoped pointers)
C++ no tiene un recolector de basura, aunque C++0x le va tomando el gustito, tenemos algunas opciones para liberarnos de liberar memoria, valga la redundancia.
Imaginemos un código como el siguiente:
#include <cstdio>
using namespace std;
class A {
public:
A() { printf("A\n"); }
~A() { printf("~A\n"); }
void hola() { printf("hola\n"); }
};
int main()
{
A* a = new A;
a->hola();
return 0;
}
El anterior programa, por más minimalista que parezca, tiene un memory leak. Aunque los recursos suelen ser liberados por el mismo sistema operativo al finalizar el programa, si el proceso se ejecuta por mucho tiempo (e.j. un servicio que se ejecuta por días, semanas o meses dentro de un servidor), los recursos se van agotando poco a poco hasta que la computadora queda hecha una pasa.
Para arreglar el leak, deberíamos escribir:
int main()
{
A* a = new A;
a->hola();
delete a; // Ahora sí liberamos la memoria
return 0;
}
¿Cómo hacemos para liberar memoria sin llamar delete
nosotros mismos?
Podemos crear una clase utilitaria que haga el trabajo por nosotros en
su destructor. Por ejemplo, en el siguiente código veremos que el
delete
es llamado en el destructor de PunteroA
:
class PunteroA {
A* ptr;
public:
PunteroA(A* p) { ptr = p; }
~PunteroA() { delete ptr; } // en el destructor borramos 'ptr'
};
int main()
{
PunteroA a(new A);
a->hola(); // error, "a" no es un puntero ni sobrecarga operator->
return 0;
}
El anterior programa nos dará un error de compilación ya que el tipo
PunteroA
no soporta el operador flecha. Debemos agregarlo en la
definición de la clase PunteroA
:
class PunteroA {
A* ptr;
public:
PunteroA(A* p) { ptr = p; }
~PunteroA() { delete ptr; }
A* operator->() { return ptr; } // operador flecha para acceder al puntero
};
int main()
{
PunteroA a(new A);
a->hola(); // ahora sí, PunteroA::operator->() nos devuelve
// el verdadero puntero A* y A::hola() finalmente
// es llamado
return 0;
}
¿Cómo obtengo el A*
desde un PunteroA
? Debemos definir el operador de conversión hacia A*
:
class PunteroA {
A* ptr;
public:
PunteroA(A* p) { ptr = p; }
~PunteroA() { delete ptr; }
A* operator->() { return ptr; }
operator A*() { return ptr; } // operador para castear a A*
};
Así podemos usar un PunteroA
en funciones que reciban un A*
. Ejemplo:
void func(A* a) { ... }
int main()
{
PunteroA a(new A);
func(a);
return 0;
}
Generalizando: Podemos generalizar nuestro PunteroA
para cualquier tipo de dato:
template<class T>
class ScopedPointer {
T* ptr;
public:
ScopedPointer(T* p) { ptr = p; }
~ScopedPointer() { delete ptr; }
operator T*() { return ptr; }
T* operator->() { return ptr; }
};
Así podemos usar el mismo ScopedPointer
para liberar instancias de cualquier clase:
int main()
{
ScopedPointer<A> a(new A);
ScopedPointer<B> b(new B);
a->hola();
return 0;
}
¿Y si quiero liberar otro tipo de recurso? Si el recurso no es
memoria, y es liberado con otra función en vez de delete
, podemos
generalizar nuestro ScopedPointer
con un nuevo parámetro de template
llamado Destroyer
:
struct DefaultDestroyer {
template<class T>
static void free(T* ptr) { delete ptr; }
};
template<class T, class Destroyer = DefaultDestroyer>
class ScopedPointer {
T* ptr;
public:
ScopedPointer(T* p) { ptr = p; }
~ScopedPointer() { Destroyer::free(ptr); }
operator T*() { return ptr; }
T* operator->() { return ptr; }
};
En este caso, el destructor ~ScopedPointer
llama a la función
estática Destroyer::free
. Esta función básicamente puede hacer lo que
nosotros queramos. Podría ser útil para liberar ficheros o cualquier
otro tipo de recurso:
struct FileDestroyer {
static void free(FILE* ptr) { fclose(ptr); }
};
int main()
{
ScopedPointer<FILE, FileDestroyer> file(fopen("hola.txt", "rt"));
char buf[256];
fread(buf, 1, 256, file);
}
¿No existen punteros de este tipo ya implementados? Boost tiene su propio scoped_ptr. También existen punteros más avanzados que cuentan referencias, como los smart pointers.