6.5. Stack con constructores y destructores

Reimplementar la lista enlazada (dentro de Stack) con constructores y destructores muestra lo bien que funcionan los constructores y destructores con new y delete. Éste es el fichero de cabecera modificado:

//: 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 Stack tiene constructor y destructor; también los tiene la clase anidada struct 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 palabra reservada new, vista en el Capítulo 4), sino que también inicializa limpiamente los punteros para ese enlace.

Puede que se pregunte por qué el destructor de Link no hace 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 se puede eliminar correctamente un puntero void si apunta 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, el destructor de Stack no los limpiará automáticamente. 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 erróneo; por eso Stack::~Stack() imprime un mensaje de error si el objeto Stack no está vacío en el momento de 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.