¿Cuándo utilizar destructores virtuales?
Tengo un conocimiento sólido de la mayoría de OOP
las teorías, pero lo que me confunde mucho son los destructores virtuales.
Pensé que siempre se llama al destructor sin importar qué y para cada objeto de la cadena.
¿Cuándo se supone que deben hacerlos virtuales y por qué?
Los destructores virtuales son útiles cuando es posible eliminar una instancia de una clase derivada a través de un puntero a la clase base:
class Base
{
// some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
Aquí notarás que no declaré que el destructor de Base fuera virtual
. Ahora, echemos un vistazo al siguiente fragmento:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
Dado que el destructor de Base no virtual
apunta b
a Base*
un Derived
objeto, delete b
tiene un comportamiento indefinido :
[En
delete b
], si el tipo estático del objeto a eliminar es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del objeto a eliminar y el tipo estático tendrá un destructor virtual o el el comportamiento no está definido .
En la mayoría de las implementaciones, la llamada al destructor se resolverá como cualquier código no virtual, lo que significa que se llamará al destructor de la clase base pero no al de la clase derivada, lo que resultará en una fuga de recursos.
En resumen, siempre cree destructores de clases base virtual
cuando deban manipularse polimórficamente.
Si desea evitar la eliminación de una instancia a través de un puntero de clase base, puede hacer que el destructor de clase base esté protegido y no sea virtual; Al hacerlo, el compilador no le permitirá llamar delete
a un puntero de clase base.
Puede obtener más información sobre la virtualidad y el destructor de clase base virtual en este artículo de Herb Sutter .
Un constructor virtual no es posible pero sí un destructor virtual. Experimentemos.....
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
El código anterior genera lo siguiente:
Base Constructor Called
Derived constructor called
Base Destructor called
La construcción del objeto derivado sigue la regla de construcción, pero cuando eliminamos el puntero "b" (puntero base) encontramos que solo se llama al destructor base. Pero esto no debe suceder. Para hacer lo correcto, tenemos que hacer virtual el destructor base. Ahora veamos qué sucede en lo siguiente:
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
virtual ~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
La salida cambió de la siguiente manera:
Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called
Entonces, la destrucción del puntero base (¡que toma una asignación en el objeto derivado!) sigue la regla de destrucción, es decir, primero el Derivado, luego la Base. Por otro lado, no hay nada como un constructor virtual.
Declarar destructores virtuales en clases base polimórficas. Este es el elemento 7 del C++ efectivo de Scott Meyers . Meyers continúa resumiendo que si una clase tiene alguna función virtual, debería tener un destructor virtual, y que las clases que no estén diseñadas para ser clases base o que no estén diseñadas para usarse polimórficamente no deberían declarar destructores virtuales.