2010-04-27 15 views
6

Hace unas horas estaba jugando con un problema de pérdida de memoria y resultó que realmente tenía algunas cosas básicas acerca de los destructores virtuales mal! Déjame explicar el diseño de mi clase.¿Cómo funcionan los destructores virtuales?

class Base 
{ 
    virtual push_elements() 
    {} 
}; 

class Derived:public Base 
{ 
vector<int> x; 
public: 
    void push_elements(){ 
     for(int i=0;i <5;i++) 
     x.push_back(i); 
    } 
}; 

void main() 
{ 
    Base* b = new Derived(); 
    b->push_elements(); 
    delete b; 
} 

La herramienta de comprobación de límites informó una pérdida de memoria en el vector de clase derivado. Y descubrí que el destructor no es virtual y no se llama al destructor de clase derivado. Y sorprendentemente se corrigió cuando hice el destructor virtual. ¿No se desasigna automáticamente el vector incluso si no se llama al destructor de clase derivado? ¿Es eso una rareza en la herramienta BoundsChecker o mi comprensión del destructor virtual es incorrecta?

+1

No formatee su código con HTML. Seleccione y presione el botón '0101' que lo sangrará por 4 espacios. – Yacoby

+0

el código como se publicó no tiene relación entre Base y Derivados – JRL

+0

@JRL: Gracias. Derived se deriva de Base. He realizado el cambio. – Prabhu

Respuesta

15

Eliminar un objeto de clase derivada a través de un puntero de clase base cuando la clase base no tiene un destructor virtual conduce a un comportamiento indefinido.

Lo que ha observado (que la parte de clase derivada del objeto nunca se destruye y, por lo tanto, sus miembros nunca se desasignan) es probablemente el más común de muchos comportamientos posibles, y un buen ejemplo de por qué es importante hacer seguro que tus destructores son virtuales cuando usas polimorfismo de esta manera.

1

Si el destructor no es virtual, se invocará el destructor base. El destructor base limpia el objeto Base y finaliza. No hay forma de que el destructor de objetos base sepa sobre el objeto derivado, debe ser el destructor derivado llamado, y la forma de hacerlo, como con cualquier función, es hacer que el destructor sea virtual.

+0

Esto no es así, como yo y otros hemos señalado, lo que obtienes es un comportamiento indefinido, lo que no necesariamente significa que se llama al destructor base. –

+1

Sin embargo, llamar al destructor de base es la manifestación más probable del comportamiento indefinido que se invoca aquí, lo que es útil saber con fines de depuración. –

+0

@Neil: Sí, pero esperaría que se llamara al destructor base la mayor parte del tiempo, al igual que esperaría 'int i = 3; i = i ++ + i ++; 'para dejar' i' conteniendo un entero pequeño sin otros cambios de estado. En este caso, es importante porque un comportamiento plausible conduce a los resultados observados y refuerza la probabilidad de que la falta de un destructor virtual sea lo que está causando el problema. Considérelo más un problema de diagnóstico que una cuestión de conformidad. –

8

Si la clase base no tiene un destructor virtual, el resultado de su código es un comportamiento indefinido, no necesariamente se llama al destructor incorrecto. Esto es presumiblemente lo que BoundsChecker está diagnosticando.

+4

Niños malcriados rechazando respuestas técnicamente correctas no es un espectáculo edificante. –

2

Aunque esto es técnicamente indefinido, aún necesita conocer el método más común de falla para diagnosticarlo. Ese método común de falla es llamar al destructor incorrecto. No conozco ninguna implementación que fallará de ninguna otra manera, aunque debo admitir que solo uso dos implementaciones.

La razón por la que esto ocurre es la misma razón por la cual se llamará a la función 'incorrecta' cuando intente anular una función miembro no virtual y la llame a través de un puntero base.

1

De C++ FAQ Lite: "¿Cuándo debería mi destructor ser virtual?" Léelo here. (C++ FAQ Lite es una excelente fuente para todas sus preguntas relacionadas con C++, por cierto).

+1

El libro de preguntas frecuentes de C++ es incluso mejor. –

1

En C++, un destructor trivial es un concepto recursivamente definido: es un destructor que el compilador escribió para usted cuando cada miembro de la clase (y cada clase base) tiene un destructor trivial. (Hay un concepto similar llamado el constructor trivial.)

Cuando un objeto con un destructor trivial está incluido en un objeto (como el vector en su ejemplo), entonces el destructor del objeto fuera (como su Derived) en es ya no es trivial Aunque no escribió destructor, el compilador de C++ escribió automáticamente un destructor que llama a los destructores de cualquier miembro que tenga destructores.

Por lo tanto, aunque no haya escrito nada, las advertencias de escribir un destructor no virtual siguen vigentes.

0

Destructor es la función miembro de la clase cuyo nombre es el mismo nombre del nombre de la clase y está precedido por el signo de tilde (~).Destructor se usa para destruir el objeto de la clase cuando el objeto se sale del alcance o se puede decir que toda la limpieza de destrucción de clase se debe hacer en destructor. Toda la memoria que se asigna durante la construcción del objeto en clase se destruye (o se libera la memoria) cuando el objeto queda fuera del alcance.

encontrar más detalles con el ejemplo BoundsCheck

1

si viene de C#, a continuación, usted tenía razón en preguntarse por qué no se asigna automáticamente des-vectorial. Pero en C++, la administración automática de la memoria no está disponible a menos que utilice Microsoft Manged Extesions para C++ (C++/CLI).

dado que no existe ningún destructor en la clase Base que sea virtual, el objeto de clase derivado nunca será liberado y por eso se filtró la memoria asignada para el miembro de datos vectoriales de la clase derivada.

Cuestiones relacionadas