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


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

Prueba.h:

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

Test.cc:

#include "test.h"

A::A() {;}
A::~A() {;}

El comando que uso 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 obtengo:

./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]

¿Algún indicio de por qué esto está emitiendo una advertencia? El destructor virtual no está en línea en absoluto. Todo lo contrario, hay una definición fuera de línea proporcionada en test.cc ¿Qué me estoy perdiendo aquí?

Editar

No creo que esta pregunta sea un duplicado de : ¿Cuál es el significado de sonido 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 me parece bien. En mi ejemplo defino el destructor (que es puro abstracto) en el archivo de implementación. Esto debería evitar que Clang emita cualquier error, incluso con -Wweak-vtables.

Author: Community, 2015-02-28

3 answers

No queremos colocar la tabla v en cada unidad de traducción. Así que debe haber algún orden de unidades de traducción, tal que podemos decir entonces, que colocamos la vtable en la "primera" unidad de traducción. Si este orden no está definido, emitimos la advertencia.

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

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

El segundo 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 de origen de Clang .

Específicamente para 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 la clase.

    struct A {
        inline virtual void x();
        virtual ~A() {
        }
    };
    void A::x() {
    }
    
  2. B::x() está en línea en la definición de la clase.

    struct B {
        virtual void x() {
        }
        virtual ~B() {
        }
    };
    
  3. C:: x () es pura virtual

    struct C {
        virtual void x() = 0;
        virtual ~C() {
        }
    };
    
  4. (Pertenece a 3.) Usted tiene un destructor virtual puro

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

    En este caso, el orden 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 vtable se colocará en la unidad de traducción donde se define la función clave.

 11
Author: overseas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-14 21:14:51

Por un momento, olvidémonos de las funciones virtuales puras e intentemos 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, comprueba si hay funciones virtuales que solo están declaradas pero no definidas dentro de la declaración de clase. Si hay exactamente una de estas funciones, el compilador sabe con seguridad que debe definirse en algún lugar (de lo contrario, el programa no se enlazará), y emite la vtable solo en la unidad de traducción que aloja 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 sola función virtual representativa es tomar la primera del conjunto candidato, y esto es lo que hace Clang.

Por lo tanto, la clave para esta optimización es seleccionar un método virtual tal que el compilador pueda garantizar que encontrará una (única) definición 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 él no está obligado a! Por lo tanto, las funciones virtuales puras no pertenecen a la lista de candidatos virtuales métodos 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 va a derivar otras clases de ella.
  2. Una subclase' destructor siempre llama a la clase base' destructor.
  3. El destructor de una clase derivada de una clase con un destructor virtual es automáticamente un destructor virtual función.
  4. Todas las funciones virtuales de todas las clases, de las que el programa crea objetos, están generalmente vinculadas al ejecutable final (incluidas las funciones virtuales que se puede demostrar estáticamente que permanecen sin usar, 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 es conceptualmente justificar.

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 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.

 13
Author: Leon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-11 14:55:07

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

Así que en lugar de

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

Utilizo

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

Luego implementa el destructor trivial en a .archivo cpp:

A::~A()
{}

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

 5
Author: Ted Percival,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-23 20:46:54