Punteros inteligentes en C++ (smart pointers) 3

En el artículo anterior nos quedamos con que hacía falta añadir nuevas funcionalidades que nos permitan mejorar las características del invento. Así que vamos a empezar poco a poco.

Para empezar, vamos a suponer que queremos poder asignar un puntero con el operador =. Para ellos hacemos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<typename _t> class PtrSimple2 {
[..]
   PtrSimple2<_t>&
   // </_t>
   operator=( const PtrSimple2<_t>& ptr ) {
      // Hay que decrementar el puntero viejo, y en caso necesario
      // liberar la memoria
      (*_count) = (*_count) - 1;
      if( *_count == 0 ) {
         delete _count;
         delete _ptr;
      }
      _count = ptr._count;
      _ptr = ptr._ptr;
      (*_count) = (*_count) + 1;
 
      return *this;
   }
[..]
};
//</_t></typename>

Con esto ya tendríamos la asignación, pero sigue existiendo un problema de base: ¿qué pasa con la herencia? Por ejemplo, tenemos una clase A y otra B que hereda de A. Si yo creo que puntero de tipo A y otro de tipo B, me gustaría poder pasar sin problemas de uno a otro y que lo reconozca (¿sería lo lógico, no?). Pues tal y como están las cosas ahora, no lo haría (haced la prueba si no me creéis). Para solventar esto, añadimos los siguientes fragmentos de código:

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
template<typename _t> class PtrSimple2 {
//</typename>
[..]
public:
   // Para poder leer el puntero sin problemas
   _t* get_ptr() {
      return _ptr;
   }
 
   // Nuevo constructor
   // Estoy simplificando algunas cosas, es posible que no se pueda acceder
   // directamente a los miembros del otro puntero
   template<typename _u> PtrSimple2( const PtrSimple2<_u>& ptr ) {
      _count = ptr._count;
      _ptr = dynamic_cast<_t *>( ptr._ptr );
      (*count) = (*count) + 1;
   } // </_t></_u></typename>
 
   // Con el operador = hay que hacer lo mismo
   template<typename _u> PtrSimple2<_t>& operator=( const PtrSimple2<_u>& ptr ) {
      // Hay que decrementar el puntero viejo, y en caso necesario
      // liberar la memoria
      (*_count) = (*_count) - 1;
      if( *_count == 0 ) {
         delete _count;
         delete _ptr;
      }
      _count = ptr._count;
      _ptr = dynamic_cast<_t *>( ptr._ptr );
      (*_count) = (*_count) + 1;
 
      return *this;
   }
[..]
};
//</_t></_u></_t></typename>

¿Con esto ya estaría todo resuelto, no? Podemos controlar la herencia, los punteros, etc. Eso sí, siempre y para todo hay que utilizar este tipo de dato. Entonces, ¿puede haber casos en los que esto no funcione? La respuesta es sí. Imaginemos que tenemos que pasar a un método el la dirección a la que apuntamos (lo que hacemos con get_ptr) y luego podemos, desde otro lado, tomar dicha dirección en otro puntero.

Esta situación tan incomoda se pude solucionar con una política aún más agresiva de control de punteros. Para esto, debemos separar la parte de los contadores de punteros de la del propio control de los punteros. Vamos a ver otra clase para hacer punteros inteligentes que sigue una política un poco más agresiva:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// ESTO VA EN LA CABECERA
 
void pointer_inc_ref( void *ptr );
bool pointer_dec_ref( void *ptr );
 
/**
 * Creates a smart pointer type that can free the memory used
 * automatically when it isn't necesary.
 * This pointer type can be very slow.
 */
template<typename _t> class pointer_to {
	Type *ptr;
 
public:
	pointer_to( _t *pointer ) {
		ptr = pointer;
		pointer_inc_ref(ptr);
	}
 
	pointer_to( const pointer_to<_t>& pointer ) {
		ptr = pointer.ptr;
		pointer_inc_ref(ptr);
	}
 
	template<typename _u> pointer_to( const pointer_to<_u>& pointer ) {
		ptr = dynamic_cast<_t *>( pointer.get() );
		pointer_inc_ref( ptr );
	}
 
	~pointer_to() {
		if( pointer_dec_ref( ptr ) )
			delete ptr;
	}
 
	_t* get() const {
		return ptr;
	}
 
	_t* operator->() const {
		return ptr;
	}
 
	_t& operator*() const {
		return *ptr;
	}
 
	// </_t>
	pointer_to<_t>& // </_t>
	operator=( const pointer_to<_t>& pointer ) {
		// </_t>
		if( pointer_dec_ref(ptr) )
			delete ptr;
		ptr = pointer.ptr;
		pointer_inc_ref(ptr);
	}
 
	pointer_to<_t>& operator=( _t*pointer ) {
		if( pointer_dec_ref(ptr) )
			delete ptr;
		ptr = pointer;
		pointer_inc_ref(ptr);
		return *this;
	}
 
	template<typename _u> pointer_to<_t>& operator=( const pointer_to<_u>& pointer ) {
		if( pointer_dec_ref(ptr) )
			delete ptr;
		ptr = dynamic_cast<_t>( pointer.ptr );
		pointer_inc_ref(ptr);
		return *this;
	}
}; // class pointer_to
// </_t></_u></_t></typename></_t></_u></typename></_t></typename>
 
 
// ESTO VA EN EL CODIGO
 
typedef map<void *, int> pointer_map_t;
typedef pointer_map_t::iterator pointer_map_iterator;
//</void>
static map<void *, int> pointer_map;
 
void pointer_inc_ref( void *ptr )
{
	if( ptr != 0 ) {
		pointer_map_iterator it;
		it = pointer_map.find( ptr );
		if( it == pointer_map.end() ) {
			pointer_map.insert(pointer_map_t::value_type(ptr, 1));
		}
		else {
			pointer_map[ptr] ++;
		}
	}
}
 
bool pointer_dec_ref( void *ptr )
{
	if( ptr != 0 ) {
		pointer_map[ptr] --;
		if( pointer_map[ptr] == 0 ) {
			pointer_map.erase(ptr);
			return true;
		}
	}
	return false;
}
//</void>

En este caso, estamos haciendo una política muy agresiva, pero ya se controlan casi todos los casos (aún pueden existir problemas, para los que la política a utilizar sería que el puntero inteligente no libere la memoria [por lo que sería una perdida utilizarlo]). Además, existen problemas de dobles enlaces que podrían provocar que la memoria nunca se libere.

Seguro que hay mejores modos de hacerlo, pero este es, el que a partir de algunas cosillas que he leído por ahí he conseguido implementar. Si hay alguna pregunta (y habéis sido capaces de leer hasta aquí) no os cortéis en hacerla.

Deja un comentario

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.