13.2. Rediseño de los ejemplos anteriores

Puede reescribirse el ejemplo Stash que vimos anteriormente en el libro, haciendo uso de los operadores new y delete, con las características que se han visto desde entonces. A la vista del nuevo código se pueden repasar estas cuestiones.

Hasta este punto del libro, ninguna de las clases Stash ni Stack poseerán los objetos a los que apuntan; es decir, cuando el objeto Stash o Stack sale de ámbito, no se invoca delete para cada uno de los objetos a los que apunta. La razón por la que eso no es posible es porque, en un intento de conseguir más generalidad, utilizan punteros void. Usar delete con punteros void libera el bloque de memoria pero, al no existir información de tipo, el compilador no sabe qué destructor debe invocar.

13.2.1. delete void* probablemente es un error

Es necesario puntualizar que, llamar a delete con un argumento void* es casi con seguridad un error en el programa, a no ser que el puntero apunte a un objeto muy simple; en particular, que no tenga un destructor. He aquí un ejemplo ilustrativo:

//: C13:BadVoidPointerDeletion.cpp
// Deleting void pointers can cause memory leaks
#include <iostream>
using namespace std;

class Object {
  void* data; // Some storage
  const int size;
  const char id;
public:
  Object(int sz, char c) : size(sz), id(c) {
    data = new char[size];
    cout << "Constructing object " << id 
         << ", size = " << size << endl;
  }
  ~Object() { 
    cout << "Destructing object " << id << endl;
    delete []data; // OK, just releases storage,
    // no destructor calls are necessary
  }
};

int main() {
  Object* a = new Object(40, 'a');
  delete a;
  void* b = new Object(40, 'b');
  delete b;
} ///:~

Listado 13.3. C13/BadVoidPointerDeletion.cpp


La clase Object contiene la variable data de tipo void* que es inicializada para apuntar a un objeto simple que no tiene destructor. En el destructor de Object se llama a delete con este puntero, sin que tenga consecuencias negativas puesto que lo único que se necesita aquí es liberar la memoria.

Ahora bien, se puede ver en main() la necesidad de que delete conozca el tipo del objeto al que apunta su argumento. Esta es la salida del programa:

	Construyendo objeto a, tamaño = 40
	Destruyendo objeto a
	Construyendo objeto b, tamaño = 40
      

Como delete sabe que a es un puntero a Object, se lleva a cabo la llamada al destructor de Object, con lo que se libera el espacio asignado a data. En cambio, cuando se manipula un objeto usando un void*, como es el caso en delete b, se libera el bloque de Object, pero no se efectúa la llamada a su destructor, con lo que tampoco se liberará el espacio asignado a data, miembro de Object. Probablemente no se mostrará ningún mensaje de advertencia al compilar el programa; no hay ningún error sintáctico. Como resultado obtenemos un programa con una silenciosa fuga de memoria.

Cuando se tiene una fuga de memoria, se debe buscar entre todas las llamadas a delete para comprobar el tipo de puntero que se le pasa. Si es un void*, puede estar ante una de las posibles causas (Sin embargo, C++ proporciona otras muchas oportunidades para la fuga de memoria).