Respuestas:
Es aún más importante para una interfaz. Cualquier usuario de su clase probablemente mantendrá un puntero a la interfaz, no un puntero a la implementación concreta. Cuando vienen a eliminarlo, si el destructor no es virtual, llamarán al destructor de la interfaz (o al valor predeterminado proporcionado por el compilador, si no especificó uno), no al destructor de la clase derivada. Fuga de memoria instantánea.
Por ejemplo
class Interface
{
virtual void doSomething() = 0;
};
class Derived : public Interface
{
Derived();
~Derived()
{
// Do some important cleanup...
}
};
void myFunc(void)
{
Interface* p = new Derived();
// The behaviour of the next line is undefined. It probably
// calls Interface::~Interface, not Derived::~Derived
delete p;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Todavía estaría indefinido si Derived usara un destructor generado implícitamente.
La respuesta a su pregunta es a menudo, pero no siempre. Si su clase abstracta prohíbe a los clientes llamar a eliminar en un puntero (o si lo dice en su documentación), tiene la libertad de no declarar un destructor virtual.
Puede prohibir a los clientes que llamen a borrar en un puntero al hacer que su destructor esté protegido. Trabajando así, es perfectamente seguro y razonable omitir un destructor virtual.
Eventualmente terminará sin una tabla de métodos virtuales, y terminará señalando a sus clientes su intención de hacer que no se pueda eliminar a través de un puntero, por lo que tiene razones para no declararlo virtual en esos casos.
[Ver ítem 4 en este artículo: http://www.gotw.ca/publications/mill18.htm ]
Decidí investigar un poco e intentar resumir tus respuestas. Las siguientes preguntas lo ayudarán a decidir qué tipo de destructor necesita:
Espero que esto ayude.
* Es importante tener en cuenta que no hay forma en C ++ de marcar una clase como final (es decir, no subclase), por lo que en el caso de que decida declarar su destructor no virtual y público, recuerde advertir explícitamente a sus compañeros programadores contra derivando de tu clase.
Referencias
Sí, siempre es importante. Las clases derivadas pueden asignar memoria o contener referencias a otros recursos que deberán limpiarse cuando se destruya el objeto. Si no proporciona a sus interfaces / clases abstractas destructores virtuales, cada vez que elimine una instancia de clase derivada a través de un identificador de clase base, no se llamará al destructor de su clase derivada.
Por lo tanto, está abriendo el potencial de pérdidas de memoria
class IFoo
{
public:
virtual void DoFoo() = 0;
};
class Bar : public IFoo
{
char* dooby = NULL;
public:
virtual void DoFoo() { dooby = new char[10]; }
void ~Bar() { delete [] dooby; }
};
IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
No siempre es obligatorio, pero creo que es una buena práctica. Lo que hace es permitir que un objeto derivado se elimine de forma segura a través de un puntero de tipo base.
Así por ejemplo:
Base *p = new Derived;
// use p as you see fit
delete p;
está mal formado si Base
no tiene un destructor virtual, porque intentará eliminar el objeto como si fuera un Base *
.
shared_ptr
intentará eliminar el objeto como si fuera un Base *
: recuerda el tipo de cosa con la que lo creó. Consulte el enlace al que se hace referencia, en particular el bit que dice "El destructor llamará a eliminar con el mismo puntero, completo con su tipo original, incluso cuando T no tiene un destructor virtual o está vacío".
No es solo una buena práctica. Es la regla # 1 para cualquier jerarquía de clases.
Ahora para el por qué. Tome la típica jerarquía animal. Los destructores virtuales pasan por el despacho virtual como cualquier otra llamada a método. Toma el siguiente ejemplo.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Suponga que Animal es una clase abstracta. La única forma en que C ++ conoce el destructor adecuado para llamar es mediante el envío de método virtual. Si el destructor no es virtual, simplemente llamará al destructor de Animal y no destruirá ningún objeto en las clases derivadas.
La razón para hacer que el destructor sea virtual en la clase base es que simplemente elimina la elección de las clases derivadas. Su destructor se vuelve virtual por defecto.
La respuesta es simple, necesita que sea virtual; de lo contrario, la clase base no sería una clase polimórfica completa.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Preferiría la eliminación anterior, pero si el destructor de la clase base no es virtual, solo se llamará al destructor de la clase base y todos los datos en la clase derivada permanecerán sin recuperar.
delete p
Invoca un comportamiento indefinido. No se garantiza llamarInterface::~Interface
.