virtuales que puro polimorfismo objeto modificador herencia funciones clase abstracta c++ clang clang++ llvm-clang

que - clang: no hay definiciones de métodos virtuales fuera de línea(clase C++ abstracta pura)



polimorfismo puro c++ (3)

Estoy tratando de compilar el siguiente código simple de C ++ usando Clang-3.5:

prueba.h:

class A { public: A(); virtual ~A() = 0; };

prueba.cc:

#include "test.h" A::A() {;} A::~A() {;}

El comando que utilizo para compilar esto (Linux, uname -r: 3.16.0-4-amd64):

$clang-3.5 -Weverything -std=c++11 -c test.cc

Y el error que me sale:

./test.h:1:7: warning: ''A'' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables]

¿Alguna pista de por qué esto está emitiendo una advertencia? El destructor virtual no está en línea en absoluto. Por el contrario, hay una definición fuera de línea proporcionada en test.cc. ¿Que me estoy perdiendo aqui?

Editar

No creo que esta pregunta sea un duplicado de: ¿Cuál es el significado de clang''s -Wweak-vtables? como sugirió Filip Roséen. En mi pregunta me refiero específicamente a las clases abstractas puras (no mencionadas en el duplicado sugerido). Sé cómo funciona -Wweak-vtables con clases no abstractas y estoy bien con eso. En mi ejemplo, defino el destructor (que es puramente abstracto) en el archivo de implementación. Esto debería evitar que Clang -Wweak-vtables errores, incluso con -Wweak-vtables .


No queremos colocar el vtable en cada unidad de traducción. Por lo tanto, debe haber algún orden de unidades de traducción, de modo que podamos decir entonces, que colocamos el vtable en la "primera" unidad de traducción. Si este pedido no está definido, emitimos la advertencia.

La respuesta la encuentras en el Itanium CXX ABI . En la sección sobre tablas virtuales (5.2.3) encontrará:

La tabla virtual para una clase se emite en el mismo objeto que contiene la definición de su función clave, es decir, la primera función virtual no pura que no está en línea en el punto de definición de la clase. Si no hay una función clave, se emite en todos los lugares utilizados. La tabla virtual emitida incluye el grupo completo de tablas virtuales para la clase, cualquier nueva tabla virtual de construcción requerida para los subobjetos y el VTT para la clase. Se emiten en un grupo COMDAT, con la tabla virtual mutilada como símbolo de identificación. Tenga en cuenta que si la función clave no se declara en línea en la definición de clase, pero su definición posterior se declara siempre en línea, se emitirá en cada objeto que contenga la definición.
NOTA : En resumen, se podría usar un destructor virtual puro como función clave, ya que debe definirse aunque sea puro. Sin embargo, el comité ABI no se dio cuenta de este hecho hasta después de que se completó la especificación de la función clave; por lo tanto, un destructor virtual puro no puede ser la función clave .

La segunda sección es la respuesta a tu pregunta. Un destructor virtual puro no es una función clave. Por lo tanto, no está claro dónde colocar el vtable y se coloca en todas partes. Como consecuencia recibimos la advertencia.

Incluso encontrarás esta explicación en la documentación fuente de Clang .

Específicamente para la advertencia: recibirá una advertencia cuando todas sus funciones virtuales pertenezcan a una de las siguientes categorías:

  1. inline se especifica para A::x() en la definición de clase.

    struct A { inline virtual void x(); virtual ~A() { } }; void A::x() { }

  2. B :: x () está en línea en la definición de clase.

    struct B { virtual void x() { } virtual ~B() { } };

  3. C :: x () es virtual puro

    struct C { virtual void x() = 0; virtual ~C() { } };

  4. (Pertenece a 3.) Tienes un destructor virtual puro

    struct D { virtual ~D() = 0; }; D::~D() { }

    En este caso, el ordenamiento podría definirse, ya que el destructor debe definirse, sin embargo, por definición, todavía no existe una "primera" unidad de traducción.

Para todos los demás casos, la función clave es la primera función virtual que no se ajusta a una de estas categorías, y la vtable se colocará en la unidad de traducción donde se define la función clave.


Por un momento, olvidemos las funciones virtuales puras y tratemos de entender cómo el compilador puede evitar emitir el vtable en todas las unidades de traducción que incluyen la declaración de una clase polimórfica.

Cuando el compilador ve la declaración de una clase con funciones virtuales, verifica si hay funciones virtuales que solo están declaradas pero no definidas dentro de la declaración de clase. Si existe exactamente una de esas funciones, el compilador sabe con seguridad que debe estar definido en algún lugar (de lo contrario, el programa no se vinculará), y emite el vtable solo en la unidad de traducción que alberga la definición de esa función. Si hay varias de estas funciones, el compilador elige una de ellas utilizando algunos criterios de selección deterministas y, con respecto a la decisión de dónde emitir el vtable, ignora las otras. La forma más sencilla de seleccionar una sola función virtual representativa es tomar la primera del conjunto candidato, y esto es lo que hace el ruido.

Entonces, la clave de esta optimización es seleccionar un método virtual para que el compilador pueda garantizar que encontrará una definición (única) de ese método en alguna unidad de traducción.

Ahora, ¿qué pasa si la declaración de clase contiene funciones virtuales puras? Un programador puede proporcionar una implementación para una función virtual pura, pero no está obligado a hacerlo . Por lo tanto, las funciones virtuales puras no pertenecen a la lista de métodos virtuales candidatos desde los cuales el compilador puede seleccionar el representativo.

Pero hay una excepción: ¡un destructor virtual puro!

Un destructor virtual puro es un caso especial:

  1. Una clase abstracta no tiene sentido si no va a derivar otras clases de ella.
  2. El destructor de una subclase siempre llama al destructor de la clase base.
  3. El destructor de una clase que se deriva de una clase con un destructor virtual es automáticamente una función virtual.
  4. Todas las funciones virtuales de todas las clases, de las que el programa crea objetos, generalmente están vinculadas al ejecutable final (incluidas las funciones virtuales que pueden demostrarse estáticamente como no utilizadas, aunque eso requeriría un análisis estático del programa completo).
  5. Por lo tanto, un destructor virtual puro debe tener una definición proporcionada por el usuario.

Por lo tanto, la advertencia de Clang en el ejemplo de la pregunta no está justificada conceptualmente.

Sin embargo, desde el punto de vista práctico, la importancia de ese ejemplo es mínima, ya que un destructor virtual puro rara vez es necesario. No puedo imaginar un caso más o menos realista en el que un destructor virtual puro no vaya acompañado de otra función virtual pura. Pero en tal configuración, la necesidad de la pureza del destructor (virtual) desaparece por completo, ya que la clase se vuelve abstracta debido a la presencia de otros métodos virtuales puros.


Terminé implementando un destructor virtual trivial, en lugar de dejarlo simplemente virtual.

Así que en lugar de

class A { public: virtual ~A() = 0; };

yo suelo

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

Luego implementa el destructor trivial en un archivo .cpp:

A::~A() {}

Esto efectivamente conecta el vtable al archivo .cpp, en lugar de enviarlo a varias unidades de traducción (objetos), y evita con éxito la advertencia de -wweak-vtables.