13.2. Rediseño de los ejemplos anteriores

Puede reescribirse el ejemplo Stash que se vio 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.

En este punto del libro, ni la clase Stash ni la 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 que, 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.4. 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. Ésta 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; el compilador supone que usted sabe lo que está haciendo. Como resultado se obtiene una fuga de memoria muy silenciosa.

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*, probablemente ha encontrado una de las causas de la fuga (sin embargo, C++ proporciona otras muchas oportunidades para la fuga de memoria).