c++ language-design virtual-destructor

c++ - ¿Hay alguna razón específica para usar destructores no virtuales?



language-design virtual-destructor (5)

Como sé, cualquier clase designada para tener subclases debe ser declarada con un destructor virtual, de modo que las instancias de la clase puedan destruirse adecuadamente cuando se accede a ellas a través de punteros.

¿Pero por qué incluso es posible declarar tal clase con un destructor no virtual? Creo que el compilador puede decidir cuándo usar los destructores virtuales. Entonces, ¿es un descuido del diseño de C ++, o me estoy perdiendo algo?


¿Hay alguna razón específica para usar destructores no virtuales?

Sí hay.

Principalmente, se reduce al rendimiento. Una función virtual no puede estar en línea, en su lugar, primero debe determinar la función correcta para invocar (que requiere información de tiempo de ejecución) y luego invocar esa función.

En el código sensible al rendimiento, la diferencia entre ningún código y una llamada a función "simple" puede marcar la diferencia. A diferencia de muchos idiomas, C ++ no asume que esta diferencia es trivial.

¿Pero por qué incluso es posible declarar tal clase con un destructor no virtual?

Porque es difícil saber (para el compilador) si la clase requiere un destructor virtual o no.

Se requiere un destructor virtual cuando:

  • invocas delete en un puntero
  • a un objeto derivado a través de una clase base

Cuando el compilador ve la definición de la clase:

  • no puede saber que pretende derivar de esta clase; después de todo, puede derivar de clases sin métodos virtuales
  • pero aún más desalentador: no puede saber que pretende invocar a delete en esta clase

Mucha gente asume que el polimorfismo requiere una novedad en la instancia, que es simplemente una falta de imaginación:

class Base { public: virtual void foo() const = 0; protected: ~Base() {} }; class Derived: public Base { public: virtual void foo() const { std::cout << "Hello, World!/n"; } }; void print(Base const& b) { b.foo(); } int main() { Derived d; print(d); }

En este caso, no hay necesidad de pagar por un destructor virtual porque no hay polimorfismo involucrado en el momento de la destrucción.

Al final, es cuestión de filosofía. Cuando sea práctico, C ++ opta por el rendimiento y el servicio mínimo de forma predeterminada (la excepción principal es RTTI).

Con respecto a la advertencia. Hay dos advertencias que pueden aprovecharse para detectar el problema:

  • -Wnon-virtual-dtor (gcc, Clang): advierte cuando una clase con función virtual no declara un destructor virtual, a menos que el destructor en la clase base esté protected . Es una advertencia pesimista, pero al menos no te pierdas nada.

  • -Wdelete-non-virtual-dtor (Clang, portado a gcc también ): avisa cuando se invoca delete en un puntero a una clase que tiene funciones virtuales pero no un destructor virtual, a menos que la clase esté marcada como final . Tiene una tasa de falsos positivos del 0%, pero advierte "tarde" (y posiblemente varias veces).



Otra razón por la que no he visto que se mencionan aquí son los límites de las DLL: desea utilizar el mismo asignador para liberar el objeto que usó para asignarlo.

Si los métodos viven en una DLL, pero el código del cliente crea una instancia del objeto con un new directo, entonces el asignador del cliente se utiliza para obtener la memoria para el objeto, pero el objeto se completa con el vtable desde la DLL, que apunta a un destructor que utiliza el asignador al que está enlazada la DLL para liberar el objeto.

Al subclasificar clases de la DLL en el cliente, el problema desaparece ya que no se usa el destructor virtual de la DLL.


Su pregunta es básicamente esta: "¿Por qué el compilador de C ++ no obliga a su destructor a ser virtual si la clase tiene miembros virtuales?" La lógica detrás de esta pregunta es que uno debería usar destructores virtuales con clases de las que intentan derivar.

Hay muchas razones por las que el compilador de C ++ no intenta superar al programador.

  1. C ++ está diseñado según el principio de obtener lo que pagas. Si quieres que algo sea virtual, debes solicitarlo. Explícitamente. Cada función en una clase que es virtual debe ser declarada explícitamente así (a menos que esté anulando una versión de clase base).

  2. Si el destructor de una clase con miembros virtuales se hiciera virtual automáticamente, ¿cómo elegiría hacerlo no virtual si eso es lo que deseaba? C ++ no tiene la capacidad de declarar explícitamente un método no virtual. Entonces, ¿cómo anularías este comportamiento impulsado por el compilador?

    ¿Existe un caso de uso válido particular para una clase virtual con un destructor no virtual? No lo sé. Tal vez hay un caso degenerado en alguna parte. Pero si lo necesitara por alguna razón, no podría decirlo bajo su sugerencia.

La pregunta que realmente debería hacerse es por qué más compiladores no emiten advertencias cuando una clase con miembros virtuales no tiene un destructor virtual. Para eso están las advertencias, después de todo.


Un destructor no virtual parece tener sentido, cuando una clase es simplemente no virtual después de todo (Nota 1).

Sin embargo, no veo ningún otro buen uso para los destructores no virtuales.

Y aprecio esa pregunta. Pregunta muy interesante!

EDITAR:

Nota 1: En casos críticos de rendimiento, puede ser favorable utilizar clases sin ninguna tabla de funciones virtuales y, por lo tanto, sin destructores virtuales en absoluto.

Por ejemplo: piense en una class Vector3 que contiene solo tres valores de coma flotante. Si la aplicación almacena una matriz de ellos, esa matriz podría almacenarse de manera compacta.

Si requerimos una tabla de funciones virtuales, Y si incluso requiriéramos almacenamiento en el montón (como en Java & co.), Entonces la matriz solo contendría los punteros a los elementos reales "ALGUIEN", en la memoria.

EDIT 2:

Incluso podemos tener un árbol de clases de herencia sin ningún método virtual.

¿Por qué?

Porque, aunque el uso de métodos "virtuales" pueda parecer un caso común y preferible , NO ES el único caso que nosotros, la humanidad, podamos imaginar.

Como en muchos detalles de ese lenguaje, C ++ le ofrece una opción. Puede elegir una de las opciones proporcionadas, por lo general, elegirá la que cualquier otra persona elija. Pero a veces no quieres esa opción!

En nuestro ejemplo, una clase Vector3 podría heredar de la clase Vector2 y aún no tendría la sobrecarga de llamadas a funciones virtuales. Pensamiento, ese ejemplo no es muy bueno;)