Puntero inteligente

En programación, un puntero inteligente (o smart pointer) es un tipo abstracto de datos que simula el comportamiento de un puntero corriente pero añadiendo nuevas características adicionales, como gestión automática de memoria y comprobador de límites (bound checking). 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 propósito de gestionar la memoria. Los punteros inteligentes se popularizaron por primera vez en el lenguaje de programación C++ durante la primera mitad de la década de 1990 como refutación a las críticas sobre la falta de recolección automática de basura en C++.[1][2]

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. Los punteros inteligentes también eliminan los punteros colgantes al posponer la destrucción hasta que el recurso ya no esté en uso.

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 o C#), el uso de los punteros inteligentes es innecesario para los aspectos de recuperación y seguridad de la administración de memoria, pero son útiles para otros fines, como la administración de residencia de la estructura de datos de caché y la gestión de recursos de objetos como descriptores de archivos o sockets de red.

Características editar

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 administración de memoria.

Los punteros inteligentes pueden facilitar la programación intencional 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, las convenciones de nomenclatura se han utilizado para resolver la ambigüedad, pero esto es un enfoque propenso a errores y laborioso,[3]​ pero esto puede ser propenso a errores. Devolviendo un auto_ptr de C++:

auto_ptr<algun_tipo> funcion_obvia1();

La función hace explícitamente que el "llamador" tenga la propiedad del resultado y, además, si no se hace nada, no se filtrará memoria. En C++11, auto_ptr ha quedado obsoleto en favor de unique_ptr.

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();


En Object Pascal (Free Pascal o Delphi), también se pueden usar los punteros inteligentes mediante TSmartPointer.

Punteros inteligentes en Boost editar

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 editar

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 método 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;
}

Shared pointer editar

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;
}

Referencias editar

Enlaces externos editar