sonido metálico:no hay definiciones de métodos virtuales fuera de línea (clase C ++ abstracta pura)

 C Programming >> Programación C >  >> Tags >> Clang
sonido metálico:no hay definiciones de métodos virtuales fuera de línea (clase C ++ abstracta pura)

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

Encontrará la respuesta en el Itanium CXX ABI. En la sección de mesas virtuales (5.2.3) encuentras:

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

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

Específicamente a la advertencia:Recibirá la 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 puramente virtual

    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, la ordenación podría definirse, porque el destructor debe definirse, sin embargo, por definición, todavía no hay una "primera" unidad de traducción.

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


Por un momento, olvidémonos de las funciones virtuales puras y tratemos de entender cómo el compilador puede evitar emitir la 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 se declaran pero no se definen dentro de la declaración de clase. Si existe exactamente una función de este tipo, el compilador sabe con certeza que debe definirse en algún lugar (de lo contrario, el programa no se vinculará) y emite la vtable solo en la unidad de traducción que alberga la definición de esa función. Si hay múltiples funciones de este tipo, el compilador elige una de ellas utilizando algunos criterios de selección deterministas y, con respecto a la decisión de dónde emitir la vtable, ignora las otras. La forma más sencilla de seleccionar una función virtual representativa única de este tipo es tomar la primera del conjunto de candidatos, y esto es lo que hace clang.

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

Ahora, ¿qué sucede si la declaración de clase contiene funciones virtuales puras? Un programador puede proporcionar una implementación para una función virtual pura, pero él no está obligado a ! Por lo tanto, las funciones virtuales puras no pertenecen a la lista de métodos virtuales candidatos de 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 vas 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 derivada 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, son generalmente vinculado al ejecutable final (incluidas las funciones virtuales que pueden demostrarse estáticamente que no se utilizan, 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 rara vez se necesita un destructor virtual puro, si es que se necesita. No puedo imaginar un caso más o menos realista donde 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 puramente virtual.

Así que en lugar de

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

Yo uso

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

Luego implemente el destructor trivial en un archivo .cpp:

A::~A()
{}

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

Como efecto secundario de declarar explícitamente el destructor, ya no obtiene las operaciones predeterminadas de copiar y mover. Consulte https://stackoverflow.com/a/29288300/954 para ver un ejemplo en el que se vuelven a declarar.