2008-11-07 22 views

Respuesta

166

Es aún más importante para una interfaz. Cualquier usuario de tu clase probablemente mantendrá un puntero a la interfaz, no un puntero a la implementación concreta. Cuando lleguen a eliminarlo, si el destructor no es virtual, invocarán el destructor de la interfaz (o el valor predeterminado proporcionado por el compilador, si no especificaste uno), no el 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; 
} 
+4

'delete p' invoca comportamiento indefinido. No se garantiza que llame a 'Interface :: ~ Interface'. – Mankarse

+0

@Mankarse: ¿puedes explicar por qué no está definido? Si Derived no implementó su propio destructor, ¿seguiría siendo un comportamiento indefinido? – Ponkadoodle

+12

@Wallacoloo: No está definido porque '[expr.delete] /': '... si el tipo estático del objeto que se va a eliminar es diferente de su tipo dinámico, ... el tipo estático tendrá un destructor virtual o el comportamiento no está definido. ... '. Todavía no estaría definido si Derived usó un destructor generado implícitamente. – Mankarse

6

Sí, siempre es importante. Las clases derivadas pueden asignar memoria o mantener la referencia a otros recursos que deberán limpiarse cuando se destruya el objeto. Si no le da a sus interfaces/clases abstractas los destructores virtuales, cada vez que elimine una instancia de clase derivada mediante un manejador de clase base, no se invocará el destructor de la clase derivada.

Por lo tanto, usted está abriendo la posibilidad 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 
+0

Cierto, de hecho, en ese ejemplo, puede que no solo se escape la memoria, sino que posiblemente se cuelgue: -/ –

+1

¿Por qué se bloqueará? –

5

Kevin, en primer lugar, por favor, no tome mi salto en sus comentarios en nuestra discusión anterior, personalmente, yo no tenía la intención para salir tan duramente. De todos modos, la respuesta principal a la pregunta.

No es siempre requerido, pero me parece una buena práctica. Lo que hace es permitir que un objeto derivado sea eliminado de manera segura mediante un puntero de tipo base.

Así, por ejemplo:

Base *p = new Derived; 
// use p as you see fit 
delete p; 

está mal formada, si la base no tiene un destructor virtual, ya que intentará eliminar el objeto como si fuera un Base *.

+0

Sin ofender. Pensé que había hecho algunos buenos comentarios y pensé que una pregunta más directa proporcionaría la misma ayuda a otra persona. Gracias. – Kevin

+0

No hay problema, es la belleza del desbordamiento de la pila, todos tienen la oportunidad de ser tanto un estudiante como un maestro :) –

+0

has hecho algunos buenos puntos :) –

5

No solo es una buena práctica. Es la regla # 1 para cualquier jerarquía de clases.

  1. La base más la clase de una jerarquía en C++ debe tener un destructor virtual

Ahora, para el qué. Tome la jerarquía animal típica. Los destructores virtuales pasan por el despacho virtual como cualquier otra llamada a método. Toma el siguiente ejemplo.

Animal* pAnimal = GetAnimal(); 
delete pAnimal; 

Supongamos 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étodos virtuales. 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.

+2

I * la mayoría de las veces * estoy de acuerdo con usted, porque * normalmente * al definir una jerarquía, puede referirse a un objeto derivado utilizando un puntero/referencia de la clase base. Pero ese no es * siempre * el caso, y en esos otros casos, puede ser suficiente para proteger a la clase base. –

+0

@j_random_hacker protegerlo no lo protegerá de las eliminaciones internas incorrectas – JaredPar

+1

@JaredPar: Correcto, pero al menos puede ser responsable en su propio código: lo difícil es asegurarse de que * el código del cliente * no pueda protegerlo causa que tu código explote (Del mismo modo, hacer que un miembro de datos sea privado no impide que el código interno haga algo estúpido con ese miembro). –

34

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 al mismo (o si así lo indica en su documentación), usted es libre de declarar un destructor virtual.

Puede prohibir a los clientes llamar a eliminar en un puntero haciendo 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 hacerlo no borrable mediante un puntero, por lo que tiene razón para no declararlo virtual en esos casos.

[Véase el punto 4 de este artículo: http://www.gotw.ca/publications/mill18.htm]

+0

. La clave para hacer que tu respuesta funcione es "en la que no se recurre a la eliminación". Por lo general, si tiene una clase base abstracta diseñada para ser una interfaz, se invocará eliminar en la clase de interfaz. –

+0

Como señaló John arriba, lo que estás sugiriendo es bastante peligroso.Confía en la suposición de que los clientes de su interfaz nunca destruirán un objeto que solo conozca el tipo de base. La única manera en que podría garantizar que no sea virtual es proteger el dtor de la clase abstracta. – Michel

+0

Michel, que he dicho hasta :) "Si lo hace, usted hace su destructor protegida. Si lo hace, los clientes no serán capaces de eliminar el uso de un puntero a esa interfaz." y, de hecho, no depende de los clientes, sino que tiene que aplicarlo diciéndoles a los clientes "no se puede hacer ...". No veo ningún peligro –

20

que decidí hacer una investigación y tratar de resumir sus respuestas. Las siguientes preguntas le ayudará a decidir qué tipo de destructor que necesita:

  1. está destinado a ser utilizado como una clase base de su clase?
    • No: Declarar destructor no virtual público para evitar v-puntero en cada objeto de la clase *.
    • Sí: Lea la siguiente pregunta.
  2. ¿Es su clase base abstracta? (es decir, cualquiera de los métodos virtuales puros?)
    • No: Trate de hacer que su clase base abstracta mediante el rediseño de
    • Sí su jerarquía de clases: Leer siguiente pregunta.
  3. ¿Desea permitir la eliminación polimórfica a través de un puntero base?
    • No: declare el destructor virtual protegido para evitar el uso no deseado.
    • Sí: declare el destructor virtual público (sin gastos indirectos en este caso).

espero que esto ayude.

* Es importante señalar que no hay forma en C++ para marcar una clase como final (es decir, no tener subclases), por lo que en el caso de que usted decide declarar su destructor no virtual y pública, recuerde explícitamente advierte a tus compañeros programadores contra derivar de tu clase.

Referencias:

+9

Esta respuesta está parcialmente desactualizada, ahora hay una palabra clave final en C++. –

3

La respuesta es simple, necesitas 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. 

prefiere la eliminación anterior, pero si destructor de la clase base no es virtual, sólo el destructor de la clase base será llamado y permanecerán sin borrar todos los datos de la clase derivada.

Cuestiones relacionadas