15.10.4. Destructores virtuales puros

Aunque los destructores virtuales puros son legales en C++ Estándar, hay una restricción añadida cuando se usan: hay que proporcionar un cuerpo de función para el destructor virtual puro. Esto parece antinatural; ¿cómo puede una función virtual ser «pura» si necesita el cuerpo de una función? Pero si se tiene en cuenta que los constructores y los destructores son operaciones especiales tiene más sentido, especialmente si se recuerda que todos los destructores en una jerarquía de clases son llamados siempre. Si se pudiera omitir la definición de un destructor virtual puro, ¿a qué cuerpo de función se llamaría durante la destrucción? Por esto, es absolutamente necesario que el compilador y el enlazador exijan la existencia del cuerpo de una función para un destructor virtual puro.

Si es puro, pero tiene que tener cuerpo, ¿qué valor tiene? La única diferencia que se verá entre el destructor virtual puro y el no-puro es que el destructor virtual puro convierte a la clase base en abstracta, por lo que no se puede crear un objeto de la clase base (aunque esto también sería verdad si cualquier otra función miembro de esa clase base fuera virtual pura).

Sin embargo, las cosas son un poco confusas cuando se hereda una clase de otra que contenga un destructor virtual puro. Al contrario que en el resto de las funciones virtuales puras, no es necesario dar una definición de un destructor virtual puro en la clase derivada. El hecho de que el siguiente código compile y enlace es la prueba:

//: C15:UnAbstract.cpp
// Pure virtual destructors 
// seem to behave strangely

class AbstractBase {
public:
  virtual ~AbstractBase() = 0;
};

AbstractBase::~AbstractBase() {}

class Derived : public AbstractBase {};
// No overriding of destructor necessary?

int main() { Derived d; } ///:~

Listado 15.13. C15/UnAbstract.cpp


Normalmente, una función virtual pura en una clase base haría que la clase derivada fuera abstracta a menos que se le diera una definición a ella (y a todas las demás funciones virtuales puras). Pero aquí no parece ser el caso. Sin embargo, hay que recordar que el compilador crea automáticamente una definición del destructor en todas las clases si no se crea una de forma explícita. Esto es lo que sucede aquí - el destructor de la clase base se está redefiniendo de forma silenciosa, por lo que es el compilador quien proporciona la definición y Derived no es realmente abstracta.

Esto plantea una cuestión interesante: ¿cuál es el sentido de un destructor virtual puro? A diferencia de una función virtual pura ordinaria, hay que darle un cuerpo de función. En una clase derivada, no se está obligado a proporcionar una definición, ya que el compilador genera automáticamente el destructor. Entonces, ¿cuál es la diferencia entre un destructor virtual normal y un destructor virtual puro?

La única diferencia aparece cuando se tiene una clase que sólo tiene una función virtual pura: el destructor. En este caso, el único efecto de la pureza del destructor es impedir la instanciación de la clase base. Si hubiera otras funciones virtuales puras, éstas impedirían la instanciación de la clase base, pero si no las hay, será el destructor virtual puro el que lo haga. Por esto, mientras que añadir un destructor virtual es esencial, el hecho de que sea puro o no lo sea no es tan importante.

Cuando se ejecuta el siguiente ejemplo, se puede ver que se llama al cuerpo de la función virtual pura después de la versión que está en la clase derivada, igual que con cualquier otro destructor:

//: C15:PureVirtualDestructors.cpp
// Pure virtual destructors
// require a function body
#include <iostream>
using namespace std;

class Pet {
public:
  virtual ~Pet() = 0;
};

Pet::~Pet() {
  cout << "~Pet()" << endl;
}

class Dog : public Pet {
public:
  ~Dog() {
    cout << "~Dog()" << endl;
  }
};

int main() {
  Pet* p = new Dog; // Upcast
  delete p; // Virtual destructor call
} ///:~

Listado 15.14. C15/PureVirtualDestructors.cpp


Como guía, cada vez que se tenga una función virtual en una clase, se debería añadir inmediatamente un destructor virtual (aunque no haga nada). De esta forma se evitan posteriores sorpresas.