6.5. Stack con constructores y destructores

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.