- Puntero inteligente
-
En programación, un puntero inteligente (o smart pointer) es un tipo abstracto de datos que simula el comportamiento de un Puntero (informática) pero añadiendo nuevas características adicionales, como recolector de basura automático y comprobador de límites. Estas características adicionales tienen como objetivo reducir errores causados por el mal uso de punteros, manteniendo la eficiencia. Los punteros inteligentes suelen llevar un registro de los objetos a los que apunta con el próposito de gestionar la memoria.
El mal uso de los punteros suele ser la mayor fuente de errores: asignaciones constantes, liberación de memoria y la referencia, que debe ser realizada por un programa usando punteros, introduce el riesgo de pérdidas de memoria. Los punteros inteligentes intentan prevenir las pérdidas de memoria, liberando automáticamente los recursos: cuando un puntero (o el último de una serie de punteros) a un objeto es destruido, porque por ejemplo se sale del ámbito, el objeto apuntado también se elimina.
Existen varios tipos de punteros inteligentes. Algunos trabajan llevando la cuenta de referencias, otros mediante asignación de un objeto a un único puntero. Si el lenguaje soporta recolector de basura automático (por ejemplo, Java), el uso de los punteros inteligentes es innecesario.
En C++, los punteros inteligentes pueden ser implementados como una "template class" que imita, mediante sobrecarga de operadores, el comportamiento de los punteros tradicionales, pero proporcionando algoritmos de administación de memoria.
Los punteros inteligentes pueden facilitar la programación internacional expresando el uso de un puntero en su propio tipo. Por ejemplo, si una función de C++ devuelve un puntero, no hay forma de saber cuando se debe liberar la memoria, cuando se ha terminado con el uso de la información.
algun_tipo* function_ambigua(); // ¿Qué se debería hacer con el resultado?
Tradicionalmente, esto se habría resuelto con comentarios, pero esto puede ser propenso a errores. Devolviendo un auto_pr de C++:
auto_ptr<algun_tipo> funcion_obvia1();
La función hace explicitamente que el "llamador" tenga la propiedad del resultado y, además, si no se hace nada, no se filtrará memoria. Del mismo modo, si la intención es devolver un puntero a un objeto gestionado en otros lugares, la función podría devolver una referencia:
algun_tipo& funcion_obvia2();
Contenido
Punteros inteligentes en Boost
La biblioteca Boost de C++ nos ofrece varios tipos de punteros inteligentes, los más importantes son:
- Scoped Pointer: Puntero no copiable
- Shared Pointer: Puntero copiable
Scoped pointer
Un scoped pointer es una clase de puntero inteligente que no puede copiarse, por lo que solo puede existir un punto de acceso al objeto que apunta. Cuando el puntero sale del ámbito, el objeto se destruye y la memoria se libera.
Sintaxis:
boost::scoped_ptr<MiClase> MiPuntero (new MiClase(1)); MiPuntero.reset(new MiClase(2));
Se puede acceder al contenido usando el operador *, acceder a la dirección con & y acceder al puntero en bruto con el metodo get().
Ejemplo:
#include <iostream> using namespace std; #include <boost/scoped_ptr.hpp> /* Vamos a crear una clase que informe de cuándo se crea y cuando se destruye, y lleve un contador de elementos creados. */ class Elemento { static int counter; int n; public: Elemento():n(++counter){ cout << "* Creando Elemento " << n << endl; }; void lanzar(const char * msg){ cout << "* Elemento " << n << " says: " << msg << endl; }; virtual ~Elemento(){ cout << "* So Long, Elemento " << n << endl; }; }; int Elemento::counter = 0; int main(int argc, char *argv[]) { /* Utilizamos corchetes para abrir un nuevo entorno (scope) Vemos que al terminar el scope, el scoped pointer se libera automáticamente, mientras que en el caso del puntero clásico, si no liberamos manulmente se produciría una fuga de memoria. */ cout << "Inicio del scope" << endl; { boost::scoped_ptr<Elemento> miElemento(new Elemento()); miElemento -> lanzar("Mensaje desde myFun_1"); Elemento * classicPointer = new Elemento(); classicPointer -> lanzar ("Mensaje del elemento con puntero clásico"); delete classicPointer; // Necesario borrarlo manualmente! } cout << "Fin del scope" << endl; /* Si tenemos un scoped pointer como atributo de una clase, o como variable suelta, es posible asignarle un valor utilizando el método reset, que borrará lo que estuviera contenido en el puntero previamente. */ boost::scoped_ptr<string> ptrCadena; ptrCadena.reset(new string("Hola")); /* Los operadores habituales se conservan. En el caso del *, se devuelve una referencia &. Para acceder al puntero en bruto se utiliza el método get(), aunque NO SE RECOMIENDA, ya que hacer modificaciones o borrar el objeto apuntado a través de get() puede producir errores. */ cout << "Longitud de cadena: " << ptrCadena -> length() << endl; cout << *ptrCadena << endl; return 0; }
Un shared pointer es un tipo de puntero inteligente que guarda un contador de referencias al objeto al que apunta. Cada vez que se hace una copia del puntero, se aumenta el contador. Cuando se destruye uno de los shared pointer, el contador disminuye.
Cuando el contador llega a cero, quiere decir que no hay más punteros apuntando al objeto, por lo que este puede destruirse y liberar la memoria que ocupa. Todo esto se hace de forma transparente al usuario.
Sintaxis:
boost::shared_ptr<MiClase> MiPuntero (new MiClase(1)); boost::shared_ptr<MiClase> OtroPuntero = MiPuntero; MiPuntero.reset(new MiClase(2));
Ejemplo:
#include <iostream> #include <string> using namespace std; int tabulados; #include <boost/shared_ptr.hpp> /* Tenemos dos clases. La clase Mirador tiene un shared pointer a Observado. */ string tab(){ return string(tabulados, '\t'); } struct Observado{ Observado(){ cout << tab() << "+ Creando Observado" << endl; } ~Observado(){ cout << tab() << "- Borrando Observado" << endl; } }; struct Mirador{ boost::shared_ptr<Observado> fan; Mirador(){ cout << tab() << "+ Creando Mirador" << endl; } ~Mirador(){ cout << tab() << "- Borrando Mirador" << endl; } }; /* La función popular rellena el atributo del Mirador con un shared pointer a Observado. */ void popular(Mirador & m1, Mirador & m2){ boost::shared_ptr<Observado> O(new Observado); m1.fan = O; m2.fan = O; } int main(int argc, char *argv[]) { tabulados = 0; cout << "-- Inicio" << endl; { tabulados ++; cout << tab() << "-- Inicio del primer scope" << endl; Mirador M1; { tabulados ++; cout << tab() << "-- Inicio del segundo scope" << endl; Mirador M2; popular(M1, M2); cout << tab() << "-- Fin del segundo scope" << endl; } tabulados --; cout << tab() << "-- Final del primer scope" << endl; } tabulados--; cout << "-- Fin" << endl; return 0; }
Enlaces externos
- Capítulo de muestra "Smart Pointers" del libro Modern C++ Design: Generic Programming and Design Patterns Applied por Andrei Alexandrescu, Addison-Wesley, 2001.
- Código de ejemplo "countptr.hpp" del libro The C++ Standard Library - A Tutorial and Reference por Nicolai M. Josuttis
- Articulo "Smart Pointers in Boost" [1]
- Articulo "The New C++: Smart(er) Pointers" por Herb Sutter
- "Smart Pointers - What, Why, Which?" por Yonat Sharon
- "Smart Pointers Overview" por John M. Dlugosz
- YASPER library otra implementación de punteros inteligentes en C++
- Smart Pointers en Delphi
Wikimedia foundation. 2010.