Mientras que los destructores virtuales puros son legales en el Standard C++, hay una restricción añadida cuando se usan: hay que proveer de un cuerpo de función a los destructores virtuales puros. 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 quita la definición de un destructor virtual puro, ¿a qué cuerpo de función se llamará durante la destrucción? Por esto, es absolutamente necesario que el compilador y el enlazador requieran la existencia del cuerpo de una función para un destructor virtual puro.
Si es puro, pero la función tiene cuerpo ¿cuál es su valor? 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 puro virtual. 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 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 causará
que la clase derivada sea abstracta a menos que esa (y todas las
demás funciones virtuales puras) tengan una definición. 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 es sobreescrito de forma oculta, y una
definición es puesta por el compilador por lo que
Derived
no es abstracta.
Esto nos brinda una cuestión interesante: ¿Cuál es el sentido de un destructor virtual puro? Al contrario que con las funciones virtuales puras ordinarias en las que hay que dar el cuerpo de una función, en una clase derivada de otra con un destructor virtual puro, no se está obligado a implementar el cuerpo de la función porque 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 ocurre 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 prevenir la instanciación de la clase base, pero si no existen otros destructores en las clase heredadas, el destructor virtual se ejecutará. Por esto, mientras que el 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.