6.2. Limpieza garantizada por el destructor

Como un programador C, a menudo pensará sobre lo importante de la inicialización, pero rara vez piensa en la limpieza. Después de todo, ¿qué hay que limpiar de un int? Simplemente, olvidarlo. Sin embargo, con las librerías, «dejarlo pasar» en un objeto cuando ya no lo necesita no es 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 pasa de él, su objeto nunca logrará 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 destructor se invoca automáticamente por el compilador 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 constructor se invoca incluso aunque utilice goto para saltar fuera del del ámbito (goto sigue existiendo en C++ por compatibilidad con C.) Debería notar que un goto no-local, implementado con las funciones setjmp y longjmp() de la librería estándar de C, evitan que el destructor sea invocado. (Eso es la especificación, incluso si su compilador no lo implementa de esa manera. Confiar un 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 esta 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.