Reimplementar la lista enlazada (dentro de
Stack
) con constructores y destructores
muestra claramente cómo costructores y destructores utilizan
new
y delete
. Éste es el
fichero de cabecera modficado:
//: C06:Stack3.h // With constructors/destructors #ifndef STACK3_H #define STACK3_H class Stack { struct Link { void* data; Link* next; Link(void* dat, Link* nxt); ~Link(); }* head; public: Stack(); ~Stack(); void push(void* dat); void* peek(); void* pop(); }; #endif // STACK3_H ///:~
Listado 6.7. C06/Stack3.h
No sólo hace que Stack
tenga un constructor
y destructor, también aparece la clase anidada
Link
.
//: C06:Stack3.cpp {O} // Constructors/destructors #include "Stack3.h" #include "../require.h" using namespace std; Stack::Link::Link(void* dat, Link* nxt) { data = dat; next = nxt; } Stack::Link::~Link() { } Stack::Stack() { head = 0; } void Stack::push(void* dat) { head = new Link(dat,head); } void* Stack::peek() { require(head != 0, "Stack empty"); return head->data; } void* Stack::pop() { if(head == 0) return 0; void* result = head->data; Link* oldHead = head; head = head->next; delete oldHead; return result; } Stack::~Stack() { require(head == 0, "Stack not empty"); } ///:~
Listado 6.8. C06/Stack3.cpp
El constructor Link:Link()
simplemente
inicializa los punteros data
y
next
, así que en
Stack::push()
, la línea:
head = new Link(dat,head);
no sólo aloja un nuevo enlace (usando creación dinámica de objetos
con la sentencia new
, vista en el capítulo 4),
también inicializa los punteros para ese enlace.
Puede que le asombre que el destructor de
Link
no haga nada - en concreto, ¿por qué
no elimina el puntero data
? Hay dos
problemas. En el capítulo 4, en el que apareció
Stack
, se decía que no puede eliminar un
puntero void
si está apuntado a un objeto (una
afirmación que se demostrará en el capítulo 13). Pero además, si
el destructor de Link
eliminara el
puntero data
, pop()
retornaría un puntero a un objeto borrado, que definitivamente
supone un error. A veces esto se considera como una cuestión de
propiedad: Link
y
por consiguiente Stack
sólo contienen los
punteros, pero no son responsables de su limpieza. Eso significa
que debe tener mucho cuidado para saber quién es el
responsable. Por ejemplo, si no invoca
pop()
y elimina todos los punteros de
Stack()
, no se limpiarán automáticamente
por el destructor de Stack
. Esto puede
ser una cuestión engorrosa y llevar a fugas de memoria, de modo
que saber quién es el responsable de la limpieza de un objeto
puede suponer la diferencia entre un programa correcto y uno
erroneo - es decir, porqué
Stack::~Stack()
imprime un mensaje de
error si el objeto Stack
no está vacío en
el momento su destrucción.
Dado que el alojamiento y limpieza de objetos
Link
está oculto dentro de
Stack
- es parte de la implementación
subyacente - no verá este suceso en el programa de prueba, aunque
será el responsable de eliminar los punteros que devuelva
pop()
:
//: C06:Stack3Test.cpp //{L} Stack3 //{T} Stack3Test.cpp // Constructors/destructors #include "Stack3.h" #include "../require.h" #include <fstream> #include <iostream> #include <string> using namespace std; int main(int argc, char* argv[]) { requireArgs(argc, 1); // File name is argument ifstream in(argv[1]); assure(in, argv[1]); Stack textlines; string line; // Read file and store lines in the stack: while(getline(in, line)) textlines.push(new string(line)); // Pop the lines from the stack and print them: string* s; while((s = (string*)textlines.pop()) != 0) { cout << *s << endl; delete s; } } ///:~
Listado 6.9. C06/Stack3Test.cpp
En este caso, todas las líneas de textlines
son
desapiladas y eliminadas, pero si no fuese así, obtendría un
mensaje de require()
que indica que hubo una
fuga de memoria.