in

c ++: referencia indefinida a vtable

apple touch icon@2

TL; DR: explica por qué puede faltar la vtable y cómo solucionarlo. La respuesta es larga porque explica por qué el compilador puede olvidarse de crear una vtable. (Editor)

Puede resultar útil saber de qué se trata el mensaje de error antes de intentar solucionarlo. Comenzaré en un nivel alto, luego trabajaré con algunos detalles más. De esa manera, las personas pueden avanzar una vez que se sientan cómodas con su comprensión de vtables. … y ahí va un montón de gente saltando hacia adelante en este momento. 🙂 Para los que se quedan:

Una vtable es básicamente la implementación más común de polimorfismo en C ++. Cuando se utilizan vtables, cada clase polimórfica tiene una vtable en algún lugar del programa; puedes pensar en ello como un (oculto) static miembro de datos de la clase. Cada objeto de una clase polimórfica está asociado con la vtable para su clase más derivada. Al marcar esta asociación, el programa puede trabajar su magia polimórfica. Advertencia importante: una vtable es un detalle de implementación. No es obligatorio por el estándar C ++, aunque la mayoría (¿todos?) Los compiladores de C ++ usan vtables para implementar el comportamiento polimórfico. Los detalles que presento son enfoques típicos o razonables. ¡Los compiladores pueden desviarse de esto!

Cada objeto polimórfico tiene un puntero (oculto) a la vtable para la clase más derivada del objeto (posiblemente múltiples punteros, en los casos más complejos). Al mirar el puntero, el programa puede decir cuál es el tipo «real» de un objeto (excepto durante la construcción, pero saltemos ese caso especial). Por ejemplo, si un objeto de tipo A no apunta a la vtable de A, entonces ese objeto es en realidad un subobjeto de algo derivado de A.

El nombre «vtable» proviene de «vfunción virtual mesa«. Es una tabla que almacena punteros a funciones (virtuales). Un compilador elige su convención sobre cómo se presenta la tabla; un enfoque simple es pasar por las funciones virtuales en el orden en que se declaran dentro de las definiciones de clase. Cuando un se llama a la función virtual, el programa sigue el puntero del objeto a una vtable, va a la entrada asociada con la función deseada, luego usa el puntero de la función almacenada para invocar la función correcta. Hay varios trucos para hacer que esto funcione, pero gané ‘ Entra en esos de aquí.

El compilador genera automáticamente una vtable (a veces se la denomina «emitida»). Un compilador podría emitir una vtable en cada unidad de traducción que vea una definición de clase polimórfica, pero eso normalmente sería una exageración innecesaria. Una alternativa (utilizado por gcc, y probablemente por otros) es elegir una sola unidad de traducción en la que colocar la vtable, de forma similar a como elegiría un solo archivo fuente en el que colocar los miembros de datos estáticos de una clase. Si este proceso de selección no selecciona ninguna unidad de traducción, la vtable se convierte en una referencia indefinida. De ahí el error, cuyo mensaje ciertamente no es particularmente claro.

De manera similar, si el proceso de selección elige una unidad de traducción, pero ese archivo de objeto no se proporciona al vinculador, la vtable se convierte en una referencia indefinida. Desafortunadamente, el mensaje de error puede ser incluso menos claro en este caso que en el caso en el que falló el proceso de selección. (Gracias a los que respondieron que mencionaron esta posibilidad. De lo contrario, probablemente lo habría olvidado).

El proceso de selección utilizado por gcc tiene sentido si partimos de la tradición de dedicar un (único) archivo fuente a cada clase que necesite uno para su implementación. Sería bueno emitir el vtable al compilar ese archivo fuente. Llamemos a eso nuestro objetivo. Sin embargo, el proceso de selección debe funcionar incluso si no se sigue esta tradición. Entonces, en lugar de buscar la implementación de toda la clase, busquemos la implementación de un miembro específico de la clase. Si se sigue la tradición – y si ese miembro está de hecho implementado – entonces esto logra el objetivo.

El miembro seleccionado por gcc (y potencialmente por otros compiladores) es la primera función virtual no en línea que no es puramente virtual. Si eres parte de la multitud que declara constructores y destructores antes que otras funciones miembro, entonces ese destructor tiene muchas posibilidades de ser seleccionado. (¿Se acordó de hacer que el destructor sea virtual, verdad?) Hay excepciones; Esperaría que las excepciones más comunes sean cuando se proporciona una definición en línea para el destructor y cuando se solicita el destructor predeterminado (usando «= default«).

El astuto podría notar que una clase polimórfica puede proporcionar definiciones en línea para todas sus funciones virtuales. ¿No hace eso que falle el proceso de selección? Lo hace en compiladores más antiguos. He leído que los últimos compiladores han abordado esta situación, pero no conozco los números de versión relevantes. Podría intentar buscar esto, pero es más fácil codificarlo o esperar a que el compilador se queje.

En resumen, hay tres causas clave del error «referencia indefinida a vtable»:

  1. A una función miembro le falta su definición.
  2. No se está vinculando un archivo de objeto.
  3. Todas las funciones virtuales tienen definiciones en línea.

Estas causas son por sí solas insuficientes para provocar el error por sí solas. Más bien, estos son los que abordaría para resolver el error. No espere que la creación intencional de una de estas situaciones produzca definitivamente este error; hay otros requisitos. Espere que al resolver estas situaciones se resuelva este error.

(Está bien, el número 3 podría haber sido suficiente cuando se hizo esta pregunta).

¡Bienvenidos de nuevo, gente que se adelanta! 🙂

  1. Mira la definición de tu clase. Busque la primera función virtual no en línea que no sea puramente virtual (no «= 0«) y cuya definición proporcione (no»= default«).
    • Si no existe tal función, intente modificar su clase para que haya una. (Error posiblemente resuelto).
    • Véase también la respuesta de Philip Thomas para una advertencia.
  2. Encuentra la definición de esa función. Si falta, ¡agrégalo! (Error posiblemente resuelto).
    • Si la definición de la función está fuera de la definición de la clase, asegúrese de que la definición de la función utilice un nombre calificado, como en ClassName::function_name.
  3. Verifique su comando de enlace. Si no menciona el archivo objeto con la definición de esa función, ¡corríjalo! (Error posiblemente resuelto).
  4. Repita los pasos 2 y 3 para cada función virtual, luego para cada función no virtual, hasta que se resuelva el error. Si todavía está atascado, repita para cada miembro de datos estáticos.

Ejemplo
Los detalles de qué hacer pueden variar y, a veces, se ramifican en preguntas separadas (como ¿Qué es una referencia indefinida / error de símbolo externo no resuelto y cómo lo soluciono?). Sin embargo, daré un ejemplo de qué hacer en un caso específico que podría confundir a los programadores más nuevos.

El paso 1 menciona la modificación de su clase para que tenga una función de cierto tipo. Si la descripción de esa función se le pasó por alto, es posible que se encuentre en la situación que pretendo abordar. Tenga en cuenta que esta es una forma de lograr el objetivo; no es la única forma, y ​​fácilmente podría haber mejores formas en su situación específica. Llamemos a tu clase A. ¿Está declarado su destructor (en la definición de su clase) como

virtual ~A() = default;

o

virtual ~A() {}

? Si es así, dos pasos cambiarán su destructor al tipo de función que queremos. Primero, cambie esa línea a

virtual ~A();

En segundo lugar, coloque la siguiente línea en un archivo fuente que sea parte de su proyecto (preferiblemente el archivo con la implementación de la clase, si tiene una):

A::~A() {}

Eso hace que su destructor (virtual) no esté en línea y no lo genere el compilador. (Siéntase libre de modificar las cosas para que coincidan mejor con el estilo de formato de su código, como agregar un comentario de encabezado a la definición de la función).

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

microsoft azure

Tutorial de Microsoft Azure

hbSC4X3LpVEhdK3QPvubCA 1200 80

8 juegos como God of War que te mantendrán aprendiendo sobre los dioses nórdicos, el aplastamiento de hachas y los problemas de papá