Tengo una sólida comprensión de la mayoría de la teoría OO, pero lo único que me confunde mucho son los destructores virtuales.
Pensé que el destructor siempre se llama sin importar qué y para cada objeto en la cadena.
¿Cuándo debes hacerlos virtuales y por qué?
virtual
se asegura de que comience en la parte superior en lugar de en el medio.Respuestas:
Los destructores virtuales son útiles cuando podría eliminar una instancia de una clase derivada a través de un puntero a la clase base:
Aquí, notará que no he declarado que el destructor de Base sea
virtual
. Ahora, echemos un vistazo al siguiente fragmento:Desde destructor de base no está
virtual
yb
es unBase*
apuntador a unDerived
objeto,delete b
tiene un comportamiento indefinido :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 provocará una pérdida de recursos.
En resumen, siempre haga los destructores de las clases base
virtual
cuando estén destinados a ser manipulados 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 virtualidad y destructor de clase base virtual en este artículo de Herb Sutter .
fuente
Base
yDerived
tengo todas las variables de almacenamiento automático? es decir, no hay un código personalizado "especial" o adicional para ejecutar en el destructor. ¿Está bien dejar de escribir algún destructor? ¿O la clase derivada todavía tendrá una pérdida de memoria?Un constructor virtual no es posible pero sí es posible un destructor virtual. Experimentemos .......
El código anterior genera lo siguiente:
La construcción del objeto derivado sigue la regla de construcción pero cuando eliminamos el puntero "b" (puntero base) hemos encontrado que solo se llama al destructor base. Pero esto no debe suceder. Para hacer lo apropiado, tenemos que hacer que el destructor base sea virtual. Ahora veamos qué sucede en lo siguiente:
La salida cambió de la siguiente manera:
Entonces, la destrucción del puntero base (¡que toma una asignación en un 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.
fuente
Declarar destructores virtuales en clases base polimórficas. Este es el ítem 7 en C ++ eficaz 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 no diseñadas para ser clases base o no diseñadas para usarse polimórficamente no deberían declarar destructores virtuales.
fuente
const Base& = make_Derived();
. En este caso, seDerived
llamará al destructor del prvalue, incluso si no es virtual, por lo que se guarda la sobrecarga introducida por vtables / vpointers. Por supuesto, el alcance es bastante limitado. Andrei Alexandrescu mencionó esto en su libro Modern C ++ Design .También tenga en cuenta que eliminar un puntero de clase base cuando no hay un destructor virtual dará como resultado un comportamiento indefinido . Algo que aprendí recientemente:
¿Cómo debe comportarse la eliminación de eliminar en C ++?
He estado usando C ++ durante años y todavía me las arreglo para ahorcarme.
fuente
Haga que el destructor sea virtual siempre que su clase sea polimórfica.
fuente
Llamar a destructor mediante un puntero a una clase base
La llamada de destructor virtual no es diferente de cualquier otra llamada de función virtual.
Para
base->f()
, la llamada se enviará aDerived::f()
, y es lo mismo parabase->~Base()
su función de anulaciónDerived::~Derived()
, se llamará.Lo mismo sucede cuando se llama al destructor indirectamente, por ejemplo
delete base;
. Ladelete
declaración llamarábase->~Base()
y se enviará aDerived::~Derived()
.Clase abstracta con destructor no virtual
Si no va a eliminar un objeto a través de un puntero a su clase base, entonces no es necesario tener un destructor virtual. Solo hazlo
protected
para que no se llame accidentalmente:fuente
~Derived()
en todas las clases derivadas, incluso si es justo~Derived() = default
? ¿O está eso implícito en el lenguaje (haciendo que sea seguro omitirlo)?protected
sección, o para asegurarse de que es virtual mediante el usooverride
.Me gusta pensar en interfaces e implementaciones de interfaces. En C ++, la interfaz de hablar es pura clase virtual. Destructor es parte de la interfaz y se espera que se implemente. Por lo tanto, destructor debe ser puramente virtual. ¿Qué tal constructor? El constructor en realidad no es parte de la interfaz porque el objeto siempre se instancia explícitamente.
fuente
virtual
en una clase base, automáticamente se encuentravirtual
en una clase derivada, incluso si no se declara así.La palabra clave virtual para destructor es necesaria cuando desea que diferentes destructores sigan el orden correcto mientras los objetos se eliminan a través del puntero de clase base. por ejemplo:
Si el destructor de la clase base es virtual, los objetos se destruirán en un orden (primero el objeto derivado y luego la base). Si el destructor de la clase base NO es virtual, solo se eliminará el objeto de la clase base (porque el puntero es de la clase base "Base * myObj"). Entonces habrá pérdida de memoria para el objeto derivado.
fuente
Para ser simple, el destructor virtual es destruir los recursos en un orden adecuado, cuando elimina un puntero de clase base que apunta a un objeto de clase derivado.
fuente
delete
a un puntero base conduce a un comportamiento indefinido.Los destructores de clase base virtuales son la "mejor práctica": siempre debe usarlos para evitar pérdidas de memoria (difíciles de detectar). Al usarlos, puede estar seguro de que todos los destructores en la cadena de herencia de sus clases están siendo llamados (en el orden correcto). La herencia de una clase base que usa el destructor virtual hace que el destructor de la clase heredada también sea automáticamente virtual (por lo que no tiene que volver a escribir 'virtual' en la declaración del destructor de la clase heredadora).
fuente
Si usa
shared_ptr
(solo shared_ptr, no unique_ptr), no tiene que tener el destructor de clase base virtual:salida:
fuente
virtual
palabra clave podría salvarte de mucha agonía.¿Qué es un destructor virtual o cómo usar un destructor virtual?
Un destructor de clase es una función con el mismo nombre de la clase que precede a ~ que reasignará la memoria asignada por la clase. ¿Por qué necesitamos un destructor virtual?
Vea el siguiente ejemplo con algunas funciones virtuales
La muestra también le dice cómo puede convertir una letra en mayúscula o minúscula
En el ejemplo anterior, puede ver que no se llama al destructor de las clases MakeUpper y MakeLower.
Vea la siguiente muestra con el destructor virtual
El destructor virtual llamará explícitamente al destructor de tiempo de ejecución más derivado de la clase para que pueda borrar el objeto de manera adecuada.
O visita el enlace
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
fuente
cuando necesita llamar al destructor de clase derivada de la clase base. necesita declarar el destructor virtual de la clase base en la clase base.
fuente
Creo que el núcleo de esta pregunta es sobre métodos virtuales y polimorfismo, no el destructor específicamente. Aquí hay un ejemplo más claro:
Imprimirá:
Sin
virtual
ella se imprimirá:Y ahora debes entender cuándo usar destructores virtuales.
fuente
B b{}; A& a{b}; a.foo();
. No es necesario verificarNULL
- lo que debería sernullptr
- antesdelete
de ingresar - con indendación incorrecta:delete nullptr;
se define como no operativo. En todo caso, deberías haberlo verificado antes de llamar->foo()
, de lo contrario, puede ocurrir un comportamiento indefinido si denew
alguna manera falla.)delete
a unNULL
puntero (es decir, no necesita laif (a != NULL)
protección).Pensé que sería beneficioso discutir el comportamiento "indefinido", o al menos el comportamiento indefinido "bloqueado" que puede ocurrir al eliminar a través de una clase base (/ struct) sin un destructor virtual, o más precisamente, no hay vtable. El siguiente código enumera algunas estructuras simples (lo mismo sería cierto para las clases).
No estoy sugiriendo si necesita destructores virtuales o no, aunque creo que en general es una buena práctica tenerlos. Solo estoy señalando la razón por la que puede terminar con un bloqueo si su clase base (/ struct) no tiene una tabla vtable y su clase derivada (/ struct) sí y elimina un objeto a través de una clase base (/ struct) puntero. En este caso, la dirección que pasa a la rutina gratuita del montón no es válida y, por lo tanto, la razón del bloqueo.
Si ejecuta el código anterior, verá claramente cuándo se produce el problema. Cuando el puntero this de la clase base (/ struct) es diferente del puntero this de la clase derivada (/ struct), se encontrará con este problema. En el ejemplo anterior, las estructuras ayb no tienen vtables. Las estructuras cyd tienen vtables. Por lo tanto, un puntero aob para una instancia de objeto ac o d se corregirá para dar cuenta de la tabla vtable. Si pasa este puntero aob para eliminarlo, se bloqueará debido a que la dirección no es válida para la rutina libre del montón.
Si planea eliminar instancias derivadas que tienen vtables de punteros de clase base, debe asegurarse de que la clase base tenga una vtable. Una forma de hacerlo es agregar un destructor virtual, que de todos modos es posible que desee limpiar adecuadamente los recursos.
fuente
Una definición básica sobre
virtual
es si determina si una función miembro de una clase puede ser anulada en sus clases derivadas.El D-tor de una clase se llama básicamente al final del alcance, pero hay un problema, por ejemplo, cuando definimos una instancia en el montón (asignación dinámica), deberíamos eliminarla manualmente.
Tan pronto como se ejecuta la instrucción, se llama al destructor de clase base, pero no para el derivado.
Un ejemplo práctico es cuando, en el campo de control, tienes que manipular efectores, actuadores.
Al final del alcance, si no se llama al destructor de uno de los elementos de poder (Actuador), habrá consecuencias fatales.
fuente
Cualquier clase que se herede públicamente, polimórfica o no, debe tener un destructor virtual. En otras palabras, si un puntero de clase base puede señalarlo, su clase base debería tener un destructor virtual.
Si es virtual, se llama al destructor de la clase derivada, luego al constructor de la clase base. Si no es virtual, solo se llama al destructor de la clase base.
fuente