¿Se heredan los destructores virtuales?


77

Si tengo una clase base con un destructor virtual. ¿Tiene una clase derivada para declarar un destructor virtual también?

class base {
public:
    virtual ~base () {}
};

class derived : base {
public:
    virtual ~derived () {} // 1)
    ~derived () {}  // 2)
};

Preguntas concretas:

  1. ¿1) y 2) son iguales? 2) ¿Es automáticamente virtual debido a su base o "detiene" la virtualidad?
  2. ¿Se puede omitir el destructor derivado si no tiene nada que hacer?
  3. ¿Cuál es la mejor práctica para declarar el destructor derivado? ¿Declararlo virtual, no virtual u omitirlo si es posible?

Respuestas:


92
  1. Sí, son iguales. La clase derivada que no declara algo virtual no impide que sea virtual. De hecho, no hay forma de evitar que un método (destructor incluido) sea virtual en una clase derivada si fuera virtual en una clase base. En> = C ++ 11 se puede utilizar finalpara evitar que se anule en clases derivadas, pero eso no impide que sea virtual.
  2. Sí, se puede omitir un destructor en una clase derivada si no tiene nada que hacer. Y no importa si es virtual o no.
  3. Lo omitiría si fuera posible. Y siempre uso la virtualpalabra clave nuevamente para funciones virtuales en clases derivadas por razones de claridad. La gente no debería tener que subir hasta arriba en la jerarquía de herencia para darse cuenta de que una función es virtual. Además, si su clase se puede copiar o mover sin tener que declarar su propia copia o mover constructores, declarar un destructor de cualquier tipo (incluso si lo define como default) lo obligará a declarar los constructores copiar y mover y los operadores de asignación si lo desea ya que el compilador ya no los colocará por usted.

Como pequeño punto para el elemento 3. Se ha señalado en los comentarios que si un destructor no se declara, el compilador genera uno predeterminado (que sigue siendo virtual). Y ese predeterminado es una función en línea.

Las funciones en línea exponen potencialmente más de su programa a cambios en otras partes de su programa y hacen que la compatibilidad binaria para bibliotecas compartidas sea complicada. Además, el aumento del acoplamiento puede resultar en una gran recompilación frente a ciertos tipos de cambios. Por ejemplo, si decide que realmente quiere una implementación para su destructor virtual, entonces cada fragmento de código que lo llamó deberá ser recompilado. Mientras que si lo hubiera declarado en el cuerpo de la clase y luego lo hubiera definido vacío en un .cpparchivo, estaría bien cambiarlo sin volver a compilar.

Mi elección personal aún sería omitirlo cuando sea posible. En mi opinión, satura el código y el compilador a veces puede hacer cosas un poco más eficientes con una implementación predeterminada sobre una vacía. Pero hay limitaciones en las que puede encontrarse que hacen que sea una mala elección.


1
No estoy de acuerdo con la parte "omitir". No cuesta mucho declararlo en el encabezado y definirlo (cuerpo vacío) en la fuente. Si lo hace, siempre puede volver y agregar algunos pasos (¿registro?) Sin forzar a sus clientes a volver a compilar.
Matthieu M.

1
En realidad, no declaro muchas funciones en línea, ni siquiera los clásicos 'accesos', pero luego, al trabajar en una gran empresa, es posible que tengamos restricciones de compatibilidad binaria más altas que la mayoría.
Matthieu M.

3
¡Acabo de aprender de esta charla que declarar el destructor virtual en realidad hará que su clase se vuelva inamovible! Entonces, cada vez que declare un destructor virtual, también debe proporcionar la regla completa de 5 si desea esas propiedades. Aún más razón para omitir cuando sea posible.
Neil Traft

1
"Además, si su clase se puede copiar o mover sin tener que declarar su propia copia o mover constructores, declarar un destructor de cualquier tipo (incluso si lo define como predeterminado) lo obligará a declarar los constructores copiar y mover y los operadores de asignación si los desea, ya que el compilador ya no los incluirá ". ¡Eso está mal! en.cppreference.com/w/cpp/language/copy_constructor
Kaiserludi

1
@Kaiserludi: verificaré que esto sea cierto y corregiré mi respuesta.
Omnifarious

2
  1. El destructor es automáticamente virtual, como con todos los métodos. No puede evitar que un método sea virtual en C ++ (si ya ha sido declarado virtual, es decir, es decir, no hay equivalente de 'final' en Java)
  2. Sí, se puede omitir.
  3. Declararía un destructor virtual si tengo la intención de que esta clase sea subclasificada, sin importar si está subclasificando a otra clase o no, también prefiero seguir declarando métodos virtuales, aunque no sea necesario. Esto mantendrá las subclases funcionando, en caso de que alguna vez decida eliminar la herencia. Pero supongo que esto es solo una cuestión de estilo.

Los destructores no son automáticamente virtuales, como tampoco lo son otras funciones miembro.

1
@Neil; Por supuesto que no, me refiero a la destructor en el ejemplo (es decir, donde la clase base tiene una virtual), no destructores en general. Y esto es cierto para todos los métodos, no solo para los destructores.
falstro

1
Desde C ++ 11, tenemos final.
whoan

1

Una función miembro virtual hará implícitamente virtual cualquier sobrecarga de esta función.

Entonces, el virtual en 1) es "opcional", el destructor de la clase base al ser virtual hace que todos los destructores secundarios también sean virtuales.


0

1 / Sí 2 / Sí, será generado por el compilador 3 / La elección entre declararlo virtual o no debe seguir su convención para miembros virtuales anulados - En mi humilde opinión, hay buenos argumentos en ambos sentidos, simplemente elija uno y sígalo.

Lo omitiría si es posible, pero hay una cosa que puede incitarte a declararlo: si usas el compilador generado, está implícitamente en línea. Hay ocasiones en las que desea evitar los miembros en línea (bibliotecas dinámicas, por ejemplo).


0

Las funciones virtuales se anulan implícitamente. Cuando el método de una clase secundaria coincide con la firma del método de la función virtual de una clase base, se anula. Esto es fácil de confundir y posiblemente romper durante la refactorización, por lo que existen overridey finalpalabras clave desde C ++ 11 para marcar este comportamiento explícitamente. Hay una advertencia correspondiente que prohíbe el comportamiento silencioso, por ejemplo -Wsuggest-overrideen GCC.

Hay una pregunta relacionada para overridey finalpalabras clave en SO: ¿Es la palabra clave 'anular' solo una verificación para un método virtual anulado? .

Y la documentación en la referencia cpp https://en.cppreference.com/w/cpp/language/override

El uso de overridepalabras clave con los destructores sigue siendo un tema de debate. Por ejemplo, consulte la discusión en esta pregunta relacionada con SO: anulación predeterminada del destructor virtual El problema es que la semántica del destructor virtual es diferente a las funciones normales. Los destructores están encadenados, por lo que todos los destructores de clases base se llaman después del hijo uno. Sin embargo, en el caso de un método regular, las implementaciones básicas del método reemplazado no se llaman por defecto. Se pueden llamar manualmente cuando sea necesario.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.