2008-11-19 22 views

Respuesta

64

No hay necesidad de utilizar un destructor virtual cuando cualquiera de las siguientes situaciones:

  • Sin intención de derivar clases de ella
  • n de instancias en el montón
  • Sin intención de almacenar en un puntero de una superclase

No hay una razón específica para evitarlo a menos que realmente esté tan presionado para la memoria.

+0

fine overview. dejé caer mi respuesta a favor de esto. +1 :) –

+15

Esta no es una buena respuesta. "No hay necesidad" es diferente de "no debería", y "sin intención" es diferente de "hecho imposible". –

+5

También agregue: no hay intención de eliminar una instancia mediante un puntero de clase base. –

23

Declaro un destructor virtual si y solo si tengo métodos virtuales. Una vez que tengo los métodos virtuales, no confío en mí mismo para evitar crear instancias en el montón o almacenar un puntero a la clase base. Ambas son operaciones extremadamente comunes y, a menudo, perderán recursos en forma silenciosa si el destructor no se declara virtual.

+3

Y, de hecho, hay una opción de advertencia en gcc que advierte precisamente que caso (métodos virtuales pero no dtor virtual). – CesarB

+3

¿No corres el riesgo de perder memoria si derivas de la clase, independientemente de si tienes otras funciones virtuales? –

+0

Estoy de acuerdo con mag. Este uso de un destructor virtual o un método virtual son requisitos separados. Virtual destructor proporciona la capacidad para que una clase realice la limpieza (por ejemplo, eliminar memoria, cerrar archivos, etc.) Y también garantiza que se llama a los constructores de todos sus miembros. – user48956

6

Se necesita un destructor virtual siempre que haya alguna posibilidad de que delete se llame en un puntero a un objeto de una subclase con el tipo de su clase. Esto asegura que se llama al destructor correcto en tiempo de ejecución sin que el compilador tenga que conocer la clase de un objeto en el montón en el momento de la compilación. Por ejemplo, supongamos B es una subclase de A:

A *x = new B; 
delete x;  // ~B() called, even though x has type A* 

Si el código no es crítica de rendimiento, sería razonable añadir un destructor virtual a cada clase base se escribe, sólo por seguridad.

Sin embargo, si se encontró delete en un círculo cerrado, la sobrecarga de rendimiento de llamar a una función virtual (incluso una que está vacía) podría ser notable. El compilador generalmente no puede incluir estas llamadas, y el procesador puede tener dificultades para predecir dónde ir. Es poco probable que esto tenga un impacto significativo en el rendimiento, pero vale la pena mencionarlo.

-7

La respuesta de rendimiento es la única que conozco que tiene la posibilidad de ser cierta. Si ha medido y descubierto que la desvirtualización de sus destructores realmente acelera las cosas, entonces probablemente tenga otras cosas en esa clase que también necesitan aceleración, pero en este punto hay consideraciones más importantes. Algún día alguien descubrirá que su código les proporcionará una buena clase base y les ahorrará una semana de trabajo. Es mejor que se asegure de que hagan el trabajo de esa semana, copien y peguen su código, en lugar de usar su código como base. Es mejor que se asegure de que algunos de sus métodos importantes sean privados para que nadie pueda heredar de usted.

+0

El polimorfismo ciertamente ralentizará las cosas. Compáralo con una situación donde necesitamos polimorfismo y eliges no hacerlo, será aún más lento. Ejemplo: implementamos toda la lógica en el destructor de la clase base, usando RTTI y una instrucción switch para limpiar los recursos. – sep

+1

En C++, no es su responsabilidad dejar de heredar de las clases que ha documentado que no son adecuadas para usar como clases base. Es mi responsabilidad usar la herencia con precaución. A menos que la guía de estilo de la casa diga lo contrario, por supuesto. –

+1

... solo hacer que el destructor sea virtual no significa que la clase necesariamente funcionará correctamente como una clase base. Entonces marcarlo virtual "solo porque", en lugar de hacer esa evaluación, es escribir un cheque que mi código no puede cobrar. –

1

Normalmente declaro que el destructor es virtual, pero si tiene un código de rendimiento crítico que se usa en un bucle interno, es posible que desee evitar la búsqueda de tabla virtual. Eso puede ser importante en algunos casos, como la verificación de colisiones. Pero ten cuidado con la forma de destruir esos objetos si usas la herencia, o destruirás solo la mitad del objeto.

Tenga en cuenta que la búsqueda de tabla virtual ocurre para un objeto si cualquier método en ese objeto es virtual. Así que no tiene sentido eliminar la especificación virtual en un destructor si tiene otros métodos virtuales en la clase.

57

Para responder a la pregunta explícitamente, es decir¿Cuándo debería no declarar un destructor virtual.

C++ '98/'03

Adición de un destructor virtual podría cambiar su clase de ser POD (plain old data) * o agregado a la no-POD. Esto puede evitar que su proyecto se compile si su tipo de clase es agregado inicializado en alguna parte.

struct A { 
    // virtual ~A(); 
    int i; 
    int j; 
}; 
void foo() { 
    A a = { 0, 1 }; // Will fail if virtual dtor declared 
} 

En un caso extremo, un cambio de este tipo también puede causar un comportamiento indefinido donde la clase se utiliza de una manera que requiere una vaina, por ejemplo, pasándolo a través de un parámetro de puntos suspensivos, o usándolo con memcpy.

void bar (...); 
void foo (A & a) { 
    bar (a); // Undefined behavior if virtual dtor declared 
} 

[* Un tipo POD es un tipo que tiene garantías específicas sobre su diseño de memoria. El estándar solo dice que si copia de un objeto con tipo POD a una matriz de caracteres (o caracteres sin signo) y viceversa, el resultado será el mismo que el del objeto original.]

Moderno C++

En versiones recientes de C++, el concepto de POD se dividió entre el diseño de clase y su construcción, copia y destrucción.

Para el caso de puntos suspensivos, que ya no es undefined behavior es ahora conditionally-supported con implementation-defined semantics (N3937 - ~ C++ '14 - 5.2.2/7):

... Pasar un argumento evaluado potencialmente del tipo de clase (Cláusula 9) que tiene un constructor de copia no trivial, un constructor de movimiento no trivial o un destructor trivial, sin el parámetro correspondiente, se admite condicionalmente con la semántica definida por la implementación.

Declarar un destructor que no sea =default significará que no es trivial (12,4/5)

... Un destructor es trivial si no es proporcionada por el usuario ...

Otros cambios en Modern C++ reducir el impacto del problema inicialización de agregados como se puede añadir un constructor:

struct A { 
    A(int i, int j); 
    virtual ~A(); 
    int i; 

    int j; 
}; 
void foo() { 
    A a = { 0, 1 }; // OK 
} 
+1

Tiene razón, y estaba equivocado, el rendimiento no es la única razón. Pero esto muestra que tenía razón sobre el resto: el programador de la clase debería incluir el código para evitar que la clase sea heredada por otra persona. –

+1

Hmm ... Estoy de acuerdo con este punto también. – sep

+0

estimado Richard, ¿puedes comentar un poco más sobre lo que has escrito? No entiendo tu punto, pero parece ser el único punto valioso que he encontrado al buscar en Google) ¿O puede ser que puedas dar un enlace a una explicación más detallada? –

5

Las funciones virtuales significan que cada objeto asignado aumenta en costo de memoria mediante un puntero de tabla de funciones virtuales.

Así que si su programa implica asignar un gran número de algún objeto, valdría la pena evitar todas las funciones virtuales para poder guardar los 32 bits adicionales por objeto.

En todos los demás casos, se ahorrará problemas de depuración para que el dtor sea virtual.

5

No todas las clases de C++ son adecuadas para usar como una clase base con polimorfismo dinámico.

Si desea que su clase sea adecuada para el polimorfismo dinámico, entonces su destructor debe ser virtual. Además, cualquier método que una subclase podría anular (lo que podría significar que todos los métodos públicos, más potencialmente algunos protegidos utilizados internamente) deben ser virtuales.

Si su clase no es adecuada para el polimorfismo dinámico, entonces el destructor no se debe marcar como virtual, porque hacerlo es engañoso. Simplemente alienta a las personas a usar su clase incorrectamente.

He aquí un ejemplo de una clase que no sería adecuado para el polimorfismo dinámico, incluso si su destructor fueron virtual:

class MutexLock { 
    mutex *mtx_; 
public: 
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); } 
    ~MutexLock() { mtx_->unlock(); } 
private: 
    MutexLock(const MutexLock &rhs); 
    MutexLock &operator=(const MutexLock &rhs); 
}; 

El objetivo de esta clase es para sentarse en la pila de RAII. Si está pasando punteros a objetos de esta clase, sin mencionar las subclases de la misma, entonces lo está haciendo mal.

+1

El uso polimórfico no implica la eliminación polimórfica. Hay muchos casos de uso para que una clase tenga métodos virtuales pero ningún destructor virtual. Considere un cuadro de diálogo típico definido estáticamente, en casi cualquier kit de herramientas GUI. La ventana principal destruirá los objetos secundarios, y conoce el tipo exacto de cada uno, aunque todas las ventanas secundarias también se usarán de forma polimórfica en cualquier cantidad de lugares, como las pruebas de impacto, el dibujo y las API de accesibilidad que buscan el texto para texto. motores de habla, etc. –

+4

Es cierto, pero el que pregunta está preguntando cuándo debe evitar específicamente un destructor virtual. Para el cuadro de diálogo que describe, un destructor virtual no tiene sentido, pero IMO no es dañino. No estoy seguro de estar seguro de que nunca necesitaré eliminar un cuadro de diálogo utilizando un puntero de clase base; por ejemplo, en el futuro, deseo que mi ventana principal cree sus objetos secundarios utilizando fábricas.Por lo tanto, no se trata de * avoid * virtual destructor, solo que no te molestes en tener uno. Sin embargo, un destructor virtual en una clase no adecuada para la derivación * es * nocivo, porque es engañoso. –

3

Si tiene una clase muy pequeña con una gran cantidad de instancias, la sobrecarga de un puntero vtable puede hacer una diferencia en el uso de la memoria de su programa. Siempre que su clase no tenga otros métodos virtuales, el destructor no virtual guardará esa sobrecarga.

0

En el funcionamiento que se realizará en la clase base, y que debe comportarse de manera virtual, debe ser virtual. Si la eliminación se puede realizar polimórficamente a través de la interfaz de la clase base, entonces debe comportarse virtualmente y ser virtual.

El destructor no necesita ser virtual si no tiene la intención de derivar de la clase. E incluso si lo hace, un destructor no virtual protegido es igual de bueno si no se requiere el borrado de punteros de clase base.

1

Si definitivamente debe asegurarse de que su clase no tiene un vtable, entonces no debe tener un destructor virtual también.

Este es un caso raro, pero sucede.

El ejemplo más familiar de un patrón que hace esto son las clases DirectX D3DVECTOR y D3DMATRIX. Estos son métodos de clase en lugar de funciones para el azúcar sintáctico, pero las clases intencionalmente no tienen un vtable para evitar la sobrecarga de la función porque estas clases se usan específicamente en el bucle interno de muchas aplicaciones de alto rendimiento.

4

Una buena razón para no declarar un destructor como virtual es cuando esto evita que su clase tenga una tabla de función virtual agregada, y debe evitar eso siempre que sea posible.

Sé que muchas personas prefieren declarar destructores como virtuales, solo para estar seguros. Pero si su clase no tiene otras funciones virtuales, realmente no tiene sentido tener un destructor virtual. Incluso si le das tu clase a otras personas que luego derivan otras clases de la misma, entonces no tendrían ninguna razón para llamar a eliminar en un puntero que se elevó a tu clase, y si lo hacen, entonces consideraría esto como un error.

Bien, hay una sola excepción, es decir, si su clase se utiliza (incorrectamente) para realizar la eliminación polimórfica de objetos derivados, pero entonces usted o los otros tipos, con suerte, sabrán que esto requiere un destructor virtual.

Dicho de otra manera, si su clase tiene un destructor no virtual, entonces esta es una afirmación muy clara: "¡No me use para eliminar objetos derivados!"

Cuestiones relacionadas