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.
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).