6.2. Limpieza garantizada por el destructor

Como programador de C, a menudo pensará en la importancia de la inicialización, pero rara vez pensará en la limpieza. Después de todo, ¿qué hay que limpiar de un int? Simplemente, olvidarlo. Sin embargo, con las librerías, «desentenderse» sin más de un objeto cuando ya no lo necesita no es tan seguro. ¿Qué ocurre si ese objeto modifica algo en el hardware, o escribe algo en pantalla, o tiene asociado espacio en el montículo (heap)? Si simplemente se olvida de él, su objeto nunca podrá cerrar sus asuntos al salir de este mundo. En C++, la limpieza es tan importante como la inicialización y por eso está garantizada por el destructor.

La sintaxis del destructor es similar a la del constructor: se usa el nombre de la clase como nombre para la función. Sin embargo, el destructor se distingue del constructor porque va precedido de una virgulilla (~). Además, el destructor nunca tiene argumentos porque la destrucción nunca necesita ninguna opción. Aquí hay una declaración de un destructor:

class Y {
public:
  ~Y();
};

El compilador invoca automáticamente al destructor cuando el objeto sale del ámbito. Puede ver dónde se invoca al constructor por el punto de la definición del objeto, pero la única evidencia de que el destructor fue invocado es la llave de cierre del ámbito al que pertenece el objeto. El destructor se invoca incluso aunque utilice goto para saltar fuera del ámbito. (goto sigue existiendo en C++ por compatibilidad con C y para las ocasiones en que resulta útil.) Tenga en cuenta que un goto no local, implementado con las funciones setjmp() y longjmp() de la librería estándar de C, no provoca la invocación de los destructores. (Eso es lo que dice la especificación, incluso si su compilador no lo implementa de esa manera. Confiar en una característica que no está en la especificación significa que su código no será portable).

A continuación, un ejemplo que demuestra las características de constructores y destructores que se han mostrado hasta el momento.

//: C06:Constructor1.cpp
// Constructors & destructors
#include <iostream>
using namespace std;

class Tree {
  int height;
public:
  Tree(int initialHeight);  // Constructor
  ~Tree();  // Destructor
  void grow(int years);
  void printsize();
};

Tree::Tree(int initialHeight) {
  height = initialHeight;
}

Tree::~Tree() {
  cout << "inside Tree destructor" << endl;
  printsize();
}

void Tree::grow(int years) {
  height += years;
}

void Tree::printsize() {
  cout << "Tree height is " << height << endl;
}

int main() {
  cout << "before opening brace" << endl;
  {
    Tree t(12);
    cout << "after Tree creation" << endl;
    t.printsize();
    t.grow(4);
    cout << "before closing brace" << endl;
  }
  cout << "after closing brace" << endl;
} ///:~

Listado 6.1. C06/Constructor1.cpp


Y ésta sería la salida del programa anterior:

antes de la llave de apertura
después de la creación de Tree
la altura del árbol es 12
antes de la llave de cierre
dentro del destructor de Tree
la altura del árbol es 16
después de la llave de cierre

Puede ver que el destructor se llama automáticamente al acabar el ámbito (llave de cierre) en el que está definido el objeto.