c++ - Destructor virtual: ¿se requiere cuando no se asigna dinámicamente la memoria?
virtual-destructor (6)
¿Necesitamos un destructor virtual si mis clases no asignan memoria dinámicamente?
p.ej
class A
{
private:
int a;
int b;
public:
A();
~A();
};
class B: public A
{
private:
int c;
int d;
public:
B();
~B();
};
En este caso, ¿necesitamos marcar el destructor de A como virtual?
El destructor de la clase padre siempre se llama automáticamente, y el dtor predeterminado siempre se genera si no hay un dtor explícito declarado. En su ejemplo, ni A ni B necesitan tener un dtor no trivial.
Si su clase tiene funciones virtuales, un administrador virtual adicional no se pierde nada y es una buena práctica. En caso de que su clase asigne memoria o cualquier otro recurso (como abrir un archivo), se necesita un dtor para liberar ese recurso nuevamente al destruirlo.
El problema no es si sus clases asignan memoria dinámicamente. Es si un usuario de las clases asigna un objeto B a través de un puntero A y luego lo elimina:
A * a = new B;
delete a;
En este caso, si no hay un destructor virtual para A, el estándar de C ++ dice que su programa exhibe un comportamiento indefinido. Esto no es bueno.
Este comportamiento se especifica en la sección 5.3.5 / 3 de la Norma (aquí se hace referencia a delete
):
Si el tipo estático del operando es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del operando y el tipo estático tendrá un destructor virtual o el comportamiento es indefinido.
El propósito de declarar destructor como virtual es poder invocar el destructor de la clase derivada cuando se llama a eliminar en un puntero de tipo Base que apunta a un objeto de tipo Derivado. No hacerlo resultaría en un comportamiento indefinido.
La suposición de que no necesita marcar el destructor como virtual si no está asignando memoria dinámicamente implica que no necesita llamar al destructor de clase derivado si no está asignando memoria dinámicamente, lo cual es incorrecto. Como todavía puede hacer varias otras operaciones en el destructor de su clase derivada que no sea simplemente desasignar la memoria asignada dinámicamente. Algunos ejemplos serían cerrar un archivo abierto, registrar alguna información, etc.
El propósito del destructor virtual (es decir, el propósito de hacer un destructor virtual ) es facilitar la eliminación polimórfica de objetos a través de la expresión de eliminación . Si su diseño no requiere la eliminación polimórfica de objetos, no necesita destructores virtuales. Refiriéndose a su ejemplo, si alguna vez tendrá que eliminar un objeto de tipo B
través de un puntero de tipo A *
(eliminación polimórfica), necesitará un destructor virtual tan alto en la jerarquía como A
Así es como se ve desde un punto de vista formal.
(Tenga en cuenta, por cierto, como dijo Neil, que lo importante es cómo crea / elimina sus objetos de clase, no cómo las clases administran su memoria interna).
En cuanto a las buenas prácticas de programación ... Depende de tu intención y tu diseño al final. Si sus clases no están diseñadas para ser polimórficas (no hay métodos virtuales en absoluto), entonces no necesita destructores virtuales. Si su clase es polimórfica (tiene al menos un método virtual), entonces hacer que el destructor sea virtual "por si acaso" podría ser una muy buena idea, y en este caso, conlleva una penalización de rendimiento / memoria prácticamente nula.
Este último generalmente se expresa como una guía de buenas prácticas bastante conocida: si su clase tiene al menos un método virtual, haga que el destructor también sea virtual. Aunque, desde el punto de vista formal, un destructor virtual podría no ser realmente necesario allí, todavía es una buena guía a seguir.
Las clases que no tienen recursos pero que pueden formar jerarquías polimórficas siempre deben definir destructores virtuales vacíos, excepto que es perfectamente suficiente para definir un destructor virtual explícito vacío (e incluso puro) en la base misma de la jerarquía. Todos los demás destructores se volverán virtuales automáticamente, incluso si están definidos implícitamente por el compilador. Es decir, no tiene que definir explícitamente un destructor vacío en cada clase. Solo la base es suficiente.
Liberar memoria no es la única función crítica que puede realizar un destructor. También se puede utilizar para restablecer el estado global, por ejemplo. No hacer esto no perderá memoria, pero podría causar otros problemas en su programa.
Además, incluso si su destructor no hace nada útil hoy, puede que en algún momento en el futuro. No hay una razón real para evitar un destructor virtual si tiene herencia, ¿por qué no simplemente agregarlo y dormir mejor por la noche?
Si su única preocupación es la memoria, tal vez debería comenzar protegiendo el destructor de la clase base (y quizás otros). Entonces, si algo no se compila, verás por qué. Ref: impulso :: de cualquier manera.