destructores - como implementar un destructor en c++
¿Cuándo no deberías usar destructores virtuales? (12)
¿Hay alguna vez una buena razón para no declarar un destructor virtual para una clase? ¿Cuándo debería evitar específicamente escribir uno?
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.
En la operación 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 pretende derivar de la clase. E incluso si lo hace, un destructor no virtual protegido es igual de bueno si no se requiere la eliminación de punteros de clase base .
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.
Las funciones virtuales significan que cada objeto asignado aumenta en costo de memoria mediante un puntero de tabla de funciones virtuales.
Entonces, si su programa implica asignar una gran cantidad 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á miseria de depuración para hacer que el dtor sea virtual.
No es necesario usar un destructor virtual cuando cualquiera de los siguientes es verdadero:
- No hay intención de derivar clases de él
- Sin instanciación en el montón
- No hay 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 por la memoria.
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.
Aquí hay un ejemplo de una clase que no sería adecuada para el polimorfismo dinámico, incluso si su destructor fuera 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 sentarse en la pila para RAII. Si está pasando punteros a objetos de esta clase, sin mencionar las subclases de la misma, entonces lo está haciendo mal.
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.
Para responder la pregunta de manera explícita, es decir, cuándo no debe declarar un destructor virtual.
C ++ ''98 / ''03
Agregar un destructor virtual puede cambiar su clase de ser POD (datos antiguos simples) * o agregado a 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, dicho cambio también puede causar un comportamiento indefinido cuando la clase se utiliza de una manera que requiere un POD, por ejemplo, pasarlo a través de un parámetro de puntos suspensivos o usarlo 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 realmente 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 objeto original.]
C ++ moderno
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, ya no se trata de undefined behavior
, ahora se conditionally-supported
con implementation-defined semantics
(N3937 - ~ C ++ ''14 - 5.2.2 / 7):
... Al pasar un argumento potencialmente evaluado de 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 implementación- semántica definida
Declarar un destructor que no sea =default
significa que no es trivial (12.4 / 5)
... Un destructor es trivial si no es proporcionado por el usuario ...
Otros cambios en Modern C ++ reducen el impacto del problema de inicialización agregada ya que se puede agregar un constructor:
struct A {
A(int i, int j);
virtual ~A ();
int i;
int j;
};
void foo () {
A a = { 0, 1 }; // OK
}
Se necesita un destructor virtual siempre que exista la posibilidad de que se delete
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 que B
es una subclase de A
:
A *x = new B;
delete x; // ~B() called, even though x has type A*
Si su código no es crítico para el rendimiento, sería razonable agregar un destructor virtual a cada clase base que escriba, solo por seguridad.
Sin embargo, si se encontró delete
muchos objetos en un bucle 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.
Si definitivamente debes asegurarte de que tu clase no tenga un vtable, entonces tampoco debes tener un destructor virtual.
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.
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.
Una buena razón para no declarar un destructor como virtual es cuando esto evita que su clase tenga una tabla de funciones virtual agregada, y debe evitar eso siempre que sea posible.
Sé que muchas personas prefieren simplemente 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.
De acuerdo, hay una sola excepción, concretamente, si su clase se utiliza (incorrectamente) para realizar la eliminación polimórfica de objetos derivados, pero entonces usted, u otros tipos, con suerte sabrá que esto requiere un destructor virtual.
Dicho de otra forma, si su clase tiene un destructor no virtual, entonces esta es una afirmación muy clara: "¡No me use para eliminar objetos derivados!"