Punteros inteligentes en C++ (smart pointers) 2

En el artículo anterior veíamos como crear una clase simple de puntero inteligente, pero no se mostró ningún ejemplo de su uso. Por este motivo, a continuación pasamos a poner un ejemplo y a ver las deficiencias de dicha clase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using std::cout;
using std::endl;
 
// Aqui iría nuestra clase de puntero simple
// template...
 
// Clase de ejemplo para mostrar el uso de los punteros inteligentes
class UnaClase {
   UnaClase() {
      cout < < "Soy UnaClase y me estan creando" << endl;
   }
   ~UnaClase() {
      cout << "Soy UnaClase y me están destruyendo" << endl;
   }
   void hacerAlgo() {
      cout << "Soy UnaClase y estoy haciendo algo útil" << endl;
   }
};
 
int main( void ) {
   PtrSimple<UnaClase> ptrUnaClase = new UnaClase();
 
   // Llamamos al método hacerAlgo de UnaClase a través del puntero inteligente.
   ptrUnaClase->hacerAlgo();
 
   // En este punto, C++ destruye la instancia a PtrSimple, eliminando automáticamente
   // la memoria ocupada por este.
}
 
// Para evitar fallos en wordpress:
// 
// </iostream>

Si hacemos la prueba, veremos como el destructor de UnaClase se llama automáticamente al término del programa. Con esto hemos conseguido nuestro primer objetivo, no tener que liberar nosotros mismos la memoria, pero ahora surgen otros problemas: ¿cómo utilizar el puntero en más partes de la aplicación que no sean el propio método?

Antes de continuar vamos a ver una cosita de C++ que nos ayudará mucho: el paso de parametros por referencia. Esto nos permite pasar a un método una referencia a un objeto y si lo modificamos estaremos modificando el objeto original. ¿Cómo se hace esto? pues muy fácil:

1
2
3
4
// Utilizamos nuestra clase UnaClase
void metodo( UnaClase& unaClase ) {
   unaClase.hacerAlgo();
}

Como vemos, al poner los argumentos del método, justo después del tipo se ha añadido un símbolo &. Éste es el que indica que es por referencia. Si no se pone nada sería por valor y si se pusiera un * sería una dirección. De este modo, ya podríamos pasar sin peligro a un método un puntero inteligente:

1
2
3
4
5
6
// Utilizamos nuestra clase UnaClase
void metodo( PtrSimple<unaclase>& unaClase ) {
   unaClase->hacerAlgo();
}
// Para evitar problemas
// </unaclase>

Ahora imaginemos el siguiente caso:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <vector>
using std::vector;
// Nuestras definiciones
 
// El codigo
typedef PtrSimple<unaclase> PtrUnaClase;
// Ojo que el worpress cambia algunas cosas (como mayúsculas a minúsculas :@)
vector<ptrunaclase> vectorPunteros;
 
// Utilizamos nuestra clase UnaClase
void metodo( PtrUnaClase& unaClase ) {
   vectorPunteros.add(unaClase);
}
 
// Para evitar problemas
// </ptrunaclase></unaclase></vector>

Estamos haciendo uso de STL para el vector. Este código no funcionará, ya que la clase vector hace uso del constructor de copia de PtrSimple, que no lo hemos definido, por lo que utilizaría el contructor de copia por defecto. Esto derivaría en que en un momento dado el puntero podría liberarse mientras en el vector seguiría existiendo un puntero al mismo, lo que supondrá un fallo de segmentación. Para solventar esto, hay que hacer uso de contadores de uso de punteros, que nos permitirán saber cuantas copias hay del mismo para liberarlo en caso necesario:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
template<typename _t> class PtrSimple2 {
private:
   _t *_ptr;
   int *_count; // Copias del puntero
 
public:
   // Creamos el puntero a partir de una zona de memoria ya existente
   PtrSimple2( _t* ptr = NULL ) {
      _ptr = ptr;
      if( _ptr == NULL )
         _count = NULL;
      else {
         _count = new int;
         *_count = 0;
      }
   }
 
   // Constructor de copia: tomamos un puntero ya existente e incrementamos
   // el contador de usos.
   PtrSimple2( const PtrSimple2<_t>& ptr ) {
      _ptr = ptr._ptr;
      _count = ptr._count;
      if( _count != NULL )
         (*count) = (*count) + 1;
   }
 
   ~PtrSimple2() {
      if( _count != NULL ) {
         (*count) = (*count) - 1;
         // Si nadie apunta a esta zona de memoria,
         // se libera la memoria
         if( (*count) == 0 )
            delete _ptr;
      }
   }
 
   _t* operator->() const {
      return _ptr;
   }
 
   _t& operator*() const {
      return *_ptr;
   }
};
 
// Para evitar fallos en wordpress
// </_t></typename>

Con la clase anterior, ya no debería haber problemas con el tema de STL ni de que hayan copias por ahí… ¿o no? Siguen existiendo casos conflictivos, por ejemplo, tener que pasar sólo la dirección (no el puntero) y luego desde otra parte del código tomar dicho puntero a una clase. Con este caso, perderiamos los contadores de uso que teníamos, por lo que perdemos el control.

A parte de lo anterior, también es constatable que nos faltan funcionalidades: y si quiero comparar dos puntero, y si quiero asignarle una dirección nueva al puntero cuando este ya tenía otra, etc. Esta y otras dudas, para el siguiente artículo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *