Referencia indefinida a vtable

357

Cuando construyo mi programa C ++, recibo el mensaje de error

referencia indefinida a 'vtable ...

¿Cuál es la causa de este problema? ¿Cómo lo soluciono?


Sucede que recibo el error para el siguiente código (La clase en cuestión es CGameModule) y no puedo entender por qué es el problema. Al principio, pensé que estaba relacionado con olvidar dar una función virtual a un cuerpo, pero por lo que entiendo, todo está aquí. La cadena de herencia es un poco larga, pero aquí está el código fuente relacionado. No estoy seguro de qué otra información debo proporcionar.

Nota: El constructor es donde está ocurriendo este error, parece.

Mi código:

class CGameModule : public CDasherModule {
 public:
  CGameModule(Dasher::CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
  : CDasherModule(pEventHandler, pSettingsStore, iID, 0, szName)
  { 
      g_pLogger->Log("Inside game module constructor");   
      m_pInterface = pInterface; 
  }

  virtual ~CGameModule() {};

  std::string GetTypedTarget();

  std::string GetUntypedTarget();

  bool DecorateView(CDasherView *pView) {
      //g_pLogger->Log("Decorating the view");
      return false;
  }

  void SetDasherModel(CDasherModel *pModel) { m_pModel = pModel; }


  virtual void HandleEvent(Dasher::CEvent *pEvent); 

 private:



  CDasherNode *pLastTypedNode;


  CDasherNode *pNextTargetNode;


  std::string m_sTargetString;


  size_t m_stCurrentStringPos;


  CDasherModel *m_pModel;


  CDasherInterfaceBase *m_pInterface;
};

Hereda de...

class CDasherModule;
typedef std::vector<CDasherModule*>::size_type ModuleID_t;

/// \ingroup Core
/// @{
class CDasherModule : public Dasher::CDasherComponent {
 public:
  CDasherModule(Dasher::CEventHandler * pEventHandler, CSettingsStore * pSettingsStore, ModuleID_t iID, int iType, const char *szName);

  virtual ModuleID_t GetID();
  virtual void SetID(ModuleID_t);
  virtual int GetType();
  virtual const char *GetName();

  virtual bool GetSettings(SModuleSettings **pSettings, int *iCount) {
    return false;
  };

 private:
  ModuleID_t m_iID;
  int m_iType;
  const char *m_szName;
};

Que hereda de ...

namespace Dasher {
  class CEvent;
  class CEventHandler;
  class CDasherComponent;
};

/// \ingroup Core
/// @{
class Dasher::CDasherComponent {
 public:
  CDasherComponent(Dasher::CEventHandler* pEventHandler, CSettingsStore* pSettingsStore);
  virtual ~CDasherComponent();

  void InsertEvent(Dasher::CEvent * pEvent);
  virtual void HandleEvent(Dasher::CEvent * pEvent) {};

  bool GetBoolParameter(int iParameter) const;
  void SetBoolParameter(int iParameter, bool bValue) const;

  long GetLongParameter(int iParameter) const;
  void SetLongParameter(int iParameter, long lValue) const;

  std::string GetStringParameter(int iParameter) const;
  void        SetStringParameter(int iParameter, const std::string & sValue) const;

  ParameterType   GetParameterType(int iParameter) const;
  std::string     GetParameterName(int iParameter) const;

 protected:
  Dasher::CEventHandler *m_pEventHandler;
  CSettingsStore *m_pSettingsStore;
};
/// @}


#endif
RyanG
fuente
¿Qué función arroja la "referencia indefinida a vtable ..."?
J. Polfer
3
Perdí totalmente que el mensaje de error especifica una función. Resulta ser el constructor, así que vi el nombre de mi clase y no hice la conexión. Entonces, el constructor está lanzando esto. Agregaré ese detalle a mi publicación original.
RyanG
3
Si no ha reconstruido los archivos de su proyecto después de realizar cambios significativos (por ejemplo, qmake -projecty luego qmake) para generar uno nuevo Makefile, es probable que se deba al error al usar Qt.
David C. Rankin
@ DavidC.Rankin, otro problema relacionado con Qt es que si el archivo con Q_OBJECTse copia externamente, pero aún no forma parte del archivo .pro, aunque se compila bien, no se vincula. Tenemos que agregar ese .h/.cpparchivo al archivo .pro para poder hacerlo qmake.
iammilind

Respuestas:

420

Las preguntas frecuentes de GCC tienen una entrada:

La solución es garantizar que se definan todos los métodos virtuales que no son puros. Tenga en cuenta que se debe definir un destructor incluso si se declara puro-virtual [class.dtor] / 7.

Alexandre Hamez
fuente
17
nm -C CGameModule.o | grep CGameModule::enumerará los métodos que se definen, suponiendo que toda la implementación de la clase vaya al archivo de objetos lógicos. Puede comparar eso con lo que se define como virtual para descubrir lo que se perdió.
Troy Daniels
133
FFS, ¿por qué el compilador no comprueba eso e imprime un mensaje de error?
Lenar Hoyt
20
Obviamente, esto solo puede ser descubierto por el enlazador, no por el compilador.
Xoph
2
En mi caso, teníamos una clase abstracta que no tenía implementación de destructor. Tuve que poner la implementación vacía ~ MyClass () {}
Shefy Gur-ary
1
Puede obtener un error como este cuando faltan objetos en el archivo que está intentando vincular (archivo libxyz.a): referencia indefinida a 'vtable for objfilename'
Kemin Zhou
162

Por lo que vale, olvidar un cuerpo en un destructor virtual genera lo siguiente:

referencia indefinida a `vtable para CYourClass '.

Estoy agregando una nota porque el mensaje de error es engañoso. (Esto fue con gcc versión 4.6.3.)

Dan
fuente
23
Tuve que poner explícitamente el cuerpo de mi destructor virtual vacío en el archivo de definición (* .cc). Tenerlo en el encabezado todavía me dio el error.
PopcornKing
44
Tenga en cuenta que una vez que agregué el destructor virtual al archivo de implementación, gcc me dijo el error real, que era un cuerpo faltante en otra función.
moodboom
1
@PopcornKing Vi el mismo problema. Incluso definir ~Destructor = default;en el archivo de encabezado no ayudó. ¿Hay un error documentado presentado contra gcc?
RD
Esto puede ser un problema diferente, pero mi problema era simplemente no tener una implementación para un destructor no virtual (estaba cambiando a punteros únicos / compartidos y lo eliminé del archivo fuente, pero no tenía una "implementación" en el encabezado )
svenevs
esto resolvió mi problema, agregar un cuerpo {} vacío para el destructor virtual evitó el error.
Bogdan Ionitza
56

Entonces, descubrí el problema y fue una combinación de mala lógica y no estar totalmente familiarizado con el mundo de automake / autotools. Estaba agregando los archivos correctos a mi plantilla Makefile.am, pero no estaba seguro de qué paso en nuestro proceso de compilación realmente creó el propio archivo MAKE. Entonces, estaba compilando un viejo archivo MAKE que no tenía idea de mis nuevos archivos.

Gracias por las respuestas y el enlace a las preguntas frecuentes de GCC. Me aseguraré de leer eso para evitar que este problema ocurra por una razón real.

RyanG
fuente
43
En resumen: el .cpp simplemente no se incluyó en la compilación. El mensaje de error es realmente engañoso.
Offirmo
67
Para usuarios de Qt: puedes obtener este mismo error si olvidas mover un encabezado.
Chris Morlier
8
Sin embargo, creo que deberías aceptar la respuesta de Alexandre Hamez. Las personas que buscan este error probablemente necesitarán su solución en lugar de la suya.
Tim
13
-1 Esta puede ser la solución a su problema, pero no es una respuesta a la pregunta original. La respuesta correcta es simplemente que no proporcionó un archivo de objeto con los símbolos requeridos. Por qué no pudo proporcionarlos es otra historia.
Walter
12
@Walter: En realidad, esta fue la respuesta exacta que estaba buscando. Los otros son obvios, y por lo tanto inútiles.
Edgar Bonet
50

Si está utilizando Qt, intente volver a ejecutar qmake. Si este error está en la clase del widget, qmake podría no haber notado que la clase ui vtable debería regenerarse. Esto solucionó el problema para mí.

gluk47
fuente
2
Acabo de eliminar toda la carpeta con build, funcionó también.
Tomáš Zato - Restablece a Mónica el
Esto no es exclusivo de qmake, tuve lo mismo con cmake. Parte del problema podría ser que ambas herramientas tienen un pequeño problema con los archivos de encabezado, lo que no siempre desencadena una reconstrucción cuando es necesario.
MSalters
2
Pensé "Reconstruir" reran qmake automáticamente ... aparentemente no. Hice "Ejecutar qmake" a su sugerencia, luego "Reconstruir" y solucionó mi problema.
yano
45

La referencia indefinida a vtable puede ocurrir debido a la siguiente situación también. Solo intenta esto:

La clase A contiene:

virtual void functionA(parameters)=0; 
virtual void functionB(parameters);

La clase B contiene:

  1. La definición de la función anteriorA.
  2. La definición de la función anterior B.

La clase C contiene: ahora está escribiendo una clase C en la que la derivará de la clase A.

Ahora, si intenta compilar, obtendrá una referencia indefinida a vtable para la clase C como error.

Razón:

functionAse define como virtual puro y su definición se proporciona en la Clase B. functionBse define como virtual (NO VIRTUAL PURO) por lo que intenta encontrar su definición en la Clase A, pero usted proporcionó su definición en la Clase B.

Solución:

  1. Haga que la función B sea puramente virtual (si tiene un requisito como ese) virtual void functionB(parameters) =0; (Esto funciona, se prueba)
  2. Proporcione la definición para la función B en la clase A, manteniéndola como virtual. (Espero que funcione ya que no probé esto)
Srinivasa Lakkaraju
fuente
@ ilya1725 Su edición sugerida no es solo corregir el formato y cosas similares, también está cambiando la respuesta, por ejemplo, diciendo que la clase C se deriva de B en lugar de A, y está cambiando la segunda solución. Esto cambia sustancialmente la respuesta. En estos casos, deje un comentario al autor. ¡Gracias!
Fabio dice reinstalar a Mónica el
@FabioTurati, ¿de qué clase hereda la clase C de entonces? La oración no es clara. Además, ¿cuál es el significado de "Clase C contiene:"?
ilya1725
@ ilya1725 Esta respuesta no es muy clara, y no estoy en contra de editarla y mejorarla. Lo que digo es que su edición cambia el significado de la respuesta, y ese es un cambio demasiado drástico. Con suerte, el autor intervendrá y aclarará lo que quiso decir (aunque ha estado inactivo durante mucho tiempo).
Fabio dice reinstalar a Mónica el
¡Gracias! En mi caso, solo tenía 2 clases en mi jerarquía. La clase A declaró un método virtual puro. La declaración de la Clase B declaró que anularía este método, pero todavía no había escrito la definición del método anulado.
Nick Desaulniers
44

Simplemente recibí este error porque mi archivo cpp no ​​estaba en el archivo MAKE.

Será
fuente
De hecho, parece que el mensaje cambia ligeramente de lo habitual undefined reference to {function/class/struct}cuando hay virtualcosas involucradas. Me tiró
Keith M
30

¿Qué es un vtable?

Puede ser útil saber de qué está hablando el mensaje de error antes de intentar solucionarlo. Comenzaré en un nivel alto, luego bajaré a algunos detalles más. De esa manera, las personas pueden saltar una vez que se sientan cómodos con su comprensión de las vtables. ... y hay un montón de gente saltando por delante en este momento. :) Para aquellos que se quedan:

Una vtable es básicamente la implementación más común del polimorfismo en C ++ . Cuando se usan vtables, cada clase polimórfica tiene una vtable en algún lugar del programa; Puedes considerarlo como un staticmiembro de datos (oculto) de la clase. Cada objeto de una clase polimórfica está asociado con la tabla vtable para su clase más derivada. Al marcar esta asociación, el programa puede hacer funcionar su magia polimórfica. Advertencia importante: una vtable es un detalle de implementación. No es un mandato del estándar C ++, aunque la mayoría (¿todos?) De los compiladores 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 tabla v para la clase más derivada del objeto (posiblemente múltiples punteros, en los casos más complejos). Al observar el puntero, el programa puede determinar cuál es el tipo "real" de un objeto (excepto durante la construcción, pero omita ese caso especial). Por ejemplo, si un objeto de tipo Ano apunta a la tabla de A, entonces ese objeto es en realidad un sub-objeto de algo derivado de A.

El nombre "vtable" viene de " v función irtual mesa ". Es una tabla que almacena punteros a funciones (virtuales). Un compilador elige su convención para la disposición de 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 se llama a una función virtual, el programa sigue el puntero del objeto a una tabla v, va a la entrada asociada con la función deseada y luego usa el puntero de función almacenado para invocar la función correcta. Hay varios trucos para hacer que esto funcione, pero no los abordaré aquí.

¿Dónde / cuándo se vtablegenera un ?

El compilador genera automáticamente una vtable (a veces llamada "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 generalmente sería una exageración innecesaria. Una alternativa ( utilizada por gcc , y probablemente por otros) es elegir una sola unidad de traducción en la que colocar la vtable, similar a cómo elegiría un único archivo fuente en el que colocar los miembros de datos estáticos de una clase. Si este proceso de selección no puede seleccionar ninguna unidad de traducción, entonces la tabla vtable se convierte en una referencia indefinida. De ahí el error, cuyo mensaje no es particularmente claro.

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

El proceso de selección utilizado por gcc tiene sentido si comenzamos con la tradición de dedicar un archivo fuente (único) a cada clase que necesita 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 se implementa de hecho , 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 una buena posibilidad 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. ¿Eso no hace que el proceso de selección falle? Lo hace en compiladores más antiguos. He leído que los últimos compiladores han abordado esta situación, pero no sé 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. Un archivo de objeto no se está vinculando.
  3. Todas las funciones virtuales tienen definiciones en línea.

Estas causas son en sí mismas insuficientes para causar el error por sí mismas. Más bien, esto es lo que abordaría para resolver el error. No espere que crear intencionalmente una de estas situaciones produzca definitivamente este error; Hay otros requisitos. Espere que resolver estas situaciones resuelva este error.

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

¿Cómo arreglar el error?

¡Bienvenido de nuevo a la gente que salta por delante! :)

  1. Mira la definición de tu clase. Encuentre 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).
    • Vea 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).
  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, ramificarse en preguntas separadas (como ¿Qué es una referencia indefinida / error de símbolo externo no resuelto y cómo lo soluciono? ). Sin embargo, proporcionaré 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 te pasó por alto, podrías estar 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. ¿Su destructor está declarado (en su definición de clase) como

virtual ~A() = default;

o

virtual ~A() {}

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

virtual ~A();

Segundo, ponga 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 uno):

A::~A() {}

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

Atasco
fuente
¡Oh, bravo! Para la explicación muy detallada y muy bien definida.
David C. Rankin
Tuve que desplazarme hacia abajo para leer esto. ¡Tenga un voto positivo por la excelente explicación!
Thomas
24

Hay mucha especulación en varias respuestas aquí. A continuación daré un código bastante mínimo que reproduce este error y explicaré por qué está ocurriendo.

Código bastante mínimo para reproducir este error

IBase.hpp

#pragma once

class IBase {
    public:
        virtual void action() = 0;
};

Derived.hpp

#pragma once

#include "IBase.hpp"

class Derived : public IBase {
    public:
        Derived(int a);
        void action() override;
};

Derived.cpp

#include "Derived.hpp"
Derived::Derived(int a) { }
void Derived::action() {}

myclass.cpp

#include <memory>
#include "Derived.hpp"

class MyClass {

    public:
        MyClass(std::shared_ptr<Derived> newInstance) : instance(newInstance) {

        }

        void doSomething() {
            instance->action();
        }

    private:
        std::shared_ptr<Derived> instance;
};

int main(int argc, char** argv) {
    Derived myInstance(5);
    MyClass c(std::make_shared<Derived>(myInstance));
    c.doSomething();
    return 0;
}

Puede compilar esto usando GCC de esta manera:

g++ -std=c++11 -o a.out myclass.cpp Derived.cpp

Ahora puede reproducir el error eliminando = 0en IBase.hpp. Me sale este error:

~/.../catkin_ws$ g++ -std=c++11 -o /tmp/m.out /tmp/myclass.cpp /tmp/Derived.cpp
/tmp/cclLscB9.o: In function `IBase::IBase(IBase const&)':
myclass.cpp:(.text._ZN5IBaseC2ERKS_[_ZN5IBaseC5ERKS_]+0x13): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o: In function `IBase::IBase()':
Derived.cpp:(.text._ZN5IBaseC2Ev[_ZN5IBaseC5Ev]+0xf): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o:(.rodata._ZTI7Derived[_ZTI7Derived]+0x10): undefined reference to `typeinfo for IBase'
collect2: error: ld returned 1 exit status

Explicación

Tenga en cuenta que el código anterior no requiere ningún destructor virtual, constructor o cualquier otro archivo adicional para que la compilación sea exitosa (aunque debería tenerlos).

La forma de entender este error es la siguiente: Linker está buscando el constructor de IBase. Este lo necesitará para el constructor de Derived. Sin embargo, como Derivado anula los métodos de IBase, tiene una tabla adjunta que hará referencia a IBase. Cuando el enlazador dice "referencia indefinida a vtable para IBase", básicamente significa que Derived tiene una referencia de vtable a IBase, pero no puede encontrar ningún código objeto compilado de IBase para admirar. Entonces, la conclusión es que la clase IBase tiene declaraciones sin implementaciones. Esto significa que un método en IBase se declara como virtual, pero olvidamos marcarlo como virtual puro O proporcionar su definición.

Consejo de despedida

Si todo lo demás falla, una forma de depurar este error es compilar un programa mínimo que compile y luego seguir cambiándolo para que llegue al estado que desea. En el medio, siga compilando para ver cuándo comienza a fallar.

Nota sobre el sistema de construcción ROS y Catkin

Si estaba compilando el conjunto de clases anterior en ROS utilizando el sistema de compilación catkin, necesitará las siguientes líneas en CMakeLists.txt:

add_executable(myclass src/myclass.cpp src/Derived.cpp)
add_dependencies(myclass theseus_myclass_cpp)
target_link_libraries(myclass ${catkin_LIBRARIES})

La primera línea básicamente dice que queremos hacer un ejecutable llamado myclass y el código para construirlo se puede encontrar en los siguientes archivos. Uno de estos archivos debe tener main (). Tenga en cuenta que no tiene que especificar archivos .hpp en ninguna parte de CMakeLists.txt. Además, no tiene que especificar Derived.cpp como biblioteca.

Shital Shah
fuente
18

Acabo de encontrar otra causa para este error que puedes verificar.

La clase base definió una función virtual pura como:

virtual int foo(int x = 0);

Y la subclase tenía

int foo(int x) override;

El problema era el error tipográfico que "=0"se suponía que debía estar fuera del paréntesis:

virtual int foo(int x) = 0;

Entonces, en caso de que te estés desplazando hacia abajo, probablemente no hayas encontrado la respuesta; esto es algo más para verificar.

Philip Thomas
fuente
12

Esto puede suceder fácilmente si olvida vincular al archivo de objeto que tiene la definición.

Hazok
fuente
1
Agregue alguna descripción más a su respuesta y posible solución.
Mohit Jain
1
Olvidar el enlace puede incluir olvidar agregar a las instrucciones de compilación. En mi caso, mi archivo cpp tenía todo perfectamente 'definido', excepto que olvidé agregar el archivo cpp a la lista de origen (en mi CMakeLists.txt, pero lo mismo puede suceder en otros sistemas de compilación, como en un archivo .pro). Como resultado, todo se compiló y luego recibí el error en el momento del enlace ...
sabio
@Mohit Jain La forma de vincular los archivos de objetos depende de la configuración del entorno y las herramientas. Desafortunadamente, una solución específica para una persona puede ser diferente para otra (por ejemplo, CMake vs herramienta patentada vs IDE vs etc.)
Hazok
11

El compilador GNU C ++ tiene que tomar una decisión sobre dónde ubicarlo vtableen caso de que tenga la definición de las funciones virtuales de un objeto distribuidas en varias unidades de compilación (por ejemplo, algunas de las definiciones de funciones virtuales de los objetos están en un archivo .cpp y otras en otro. archivo cpp, y así sucesivamente).

El compilador elige colocar el vtableen el mismo lugar donde se define la primera función virtual declarada.

Ahora, si por alguna razón olvidó proporcionar una definición para la primera función virtual declarada en el objeto (u olvidó por error agregar el objeto compilado en la fase de enlace), obtendrá este error.

Como efecto secundario, tenga en cuenta que solo para esta función virtual en particular no obtendrá el error de enlazador tradicional como si le faltara la función foo .

Iulian Popa
fuente
8

No para cruzar el poste pero. Si se trata de herencia, el segundo hit de Google fue lo que me había perdido, es decir. Todos los métodos virtuales deben ser definidos.

Como:

virtual void fooBar() = 0;

Consulte Answare C ++ Referencia indefinida a vtable y herencia para más detalles. Acabo de darme cuenta de que ya se mencionó anteriormente, pero diablos podría ayudar a alguien.

Victor Häggqvist
fuente
8

Ok, la solución a esto es que es posible que se haya perdido la definición. Consulte el siguiente ejemplo para evitar el error del compilador de vtable:

// In the CGameModule.h

class CGameModule
{
public:
    CGameModule();
    ~CGameModule();

    virtual void init();
};

// In the CGameModule.cpp

#include "CGameModule.h"

CGameModule::CGameModule()
{

}

CGameModule::~CGameModule()
{

}

void CGameModule::init()    // Add the definition
{

}
Sammy
fuente
7
  • ¿Estás seguro de que CDasherComponenttiene un cuerpo para el destructor? Definitivamente no está aquí; la pregunta es si está en el archivo .cc.
  • Desde una perspectiva de estilo, CDasherModuledebe definir explícitamente su destructor virtual.
  • Parece que CGameModuletiene un extra } al final (después del }; // for the class).
  • ¿Se CGameModuleestá vinculando contra las bibliotecas que definen CDasherModuley CDasherComponent?
Stephen
fuente
- Sí, CDasherComponent tiene un cuerpo destructor en el cpp. Pensé que se declaró en el .h cuando publiqué esto. - Debidamente notado. - Ese fue un soporte adicional que agregué por error al eliminar la documentación. - Por lo que yo entiendo, sí. He estado modificando un archivo de automake que no escribí, pero he seguido los patrones que han funcionado para otras clases con el mismo patrón de herencia de las mismas clases, así que a menos que haya cometido un error estúpido (Totalmente posible) No creo que sea eso.
RyanG
@RyanG: intente mover todas las definiciones de funciones virtuales a la definición de clase. Asegúrese de que estén todos allí y vea si el resultado cambia.
Stephen
5

¿Quizás perder el destructor virtual es un factor contribuyente?

virtual ~CDasherModule(){};
DevByStarlight
fuente
5

Este fue el primer resultado de búsqueda para mí, así que pensé en agregar otra cosa para verificar: asegurarme de que la definición de funciones virtuales esté realmente en la clase. En mi caso, tuve esto:

Archivo de cabecera:

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

class B : public A {
 public:
  void foo() override;
};

y en mi archivo .cc:

void foo() {
  ...
}

Esto debería leer

void B::foo() {
}
RedSpikeyThing
fuente
3

Tal vez no. Definitivamente ~CDasherModule() {}falta.

Bretzelus
fuente
3

Tantas respuestas aquí, pero ninguna de ellas parecía haber cubierto cuál era mi problema. Tuve lo siguiente:


class I {
    virtual void Foo()=0;
};

Y en otro archivo (incluido en la compilación y vinculación, por supuesto)

class C : public I{
    void Foo() {
        //bar
    }
};

Bueno, esto no funcionó y recibí el error del que todos hablan. Para resolverlo, tuve que mover la definición real de Foo fuera de la declaración de clase como tal:

class C : public I{
    void Foo();
};

C::Foo(){
   //bar
}

No soy un gurú de C ++, así que no puedo explicar por qué esto es más correcto, pero resolvió el problema para mí.

Nathan Daniels
fuente
No soy un gurú de C ++, pero parece estar relacionado con mezclar la declaración y la definición en el mismo archivo, con un archivo de definición adicional.
Terry G Lorber
1
Cuando la definición de la función estaba dentro de la definición de su clase, se declaraba implícitamente "en línea". En ese momento, todas sus funciones virtuales estaban en línea. Cuando movió la función fuera de la definición de clase, ya no era una función "en línea". Como tenía una función virtual no en línea, su compilador sabía dónde emitir la vtable. (Una explicación más completa no cabe en un comentario.)
JaMiT
3

Así que estaba usando Qt con Windows XP y el compilador MinGW y esto me estaba volviendo loco.

Básicamente, el moc_xxx.cpp se generó vacío incluso cuando me agregaron

Q_OBJECT

Eliminar todo lo que hace que las funciones sean virtuales, explícitas y cualquier cosa que adivines no funcionó. Finalmente comencé a eliminar línea por línea y resultó que tenía

#ifdef something

Alrededor del archivo. Incluso cuando el #ifdef era verdadero, no se generó el archivo moc.

Entonces, eliminar todos los #ifdefs solucionó el problema.

Esto no sucedía con Windows y VS 2013.

Daniel Georgiev
fuente
Al comentar la línea Q_OBJECT, mi aplicación de prueba simple se compiló con un plano g++ *.cpp .... (Necesitaba algo rápido y sucio, pero qmake estaba lleno de dolor.)
Nathan Kidd
2

Si todo lo demás falla, busque duplicación. La referencia inicial explícita a los constructores y destructores me dirigió mal hasta que leí una referencia en otra publicación. Es cualquier método no resuelto. En mi caso, pensé que había reemplazado la declaración que usaba char * xml como parámetro con una que usaba el const char * xml innecesariamente problemático, pero en su lugar, había creado una nueva y había dejado la otra en su lugar.

Jerry Miller
fuente
2

Se mencionan muchas posibilidades para causar este error, y estoy seguro de que muchas de ellas causan el error. En mi caso, había otra definición de la misma clase, debido a una duplicación del archivo fuente. Este archivo fue compilado, pero no vinculado, por lo que el vinculador se quejaba de no poder encontrarlo.

Para resumir, diría que si ha observado la clase lo suficiente y no puede ver qué posible problema de sintaxis podría estar causando, busque problemas de compilación como un archivo faltante o un archivo duplicado.

crw4096
fuente
2

En mi caso, estoy usando Qt y había definido una QObjectsubclase en un archivo foo.cpp(no .h). La solución fue agregar #include "foo.moc"al final de foo.cpp.

Stefan Monov
fuente
2

Creo que también vale la pena mencionar que también recibirá el mensaje cuando intente vincular al objeto de cualquier clase que tenga al menos un método virtual y el vinculador no puede encontrar el archivo. Por ejemplo:

Foo.hpp:

class Foo
{
public:
    virtual void StartFooing();
};

Foo.cpp:

#include "Foo.hpp"

void Foo::StartFooing(){ //fooing }

Compilado con:

g++ Foo.cpp -c

Y main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Compilado y vinculado con:

g++ main.cpp -o main

Da nuestro error favorito:

/tmp/cclKnW0g.o: en la función main': main.cpp:(.text+0x1a): undefined reference tovtable para Foo 'collect2: error: ld devolvió 1 estado de salida

Esto ocurre desde mi causa excepcional:

  1. Vtable se crea por clase en tiempo de compilación

  2. Linker no tiene acceso a vtable que está en Foo.o

Adam Stepniak
fuente
1

Recibí este error en el siguiente escenario

Considere un caso en el que ha definido la implementación de funciones miembro de una clase en el archivo de encabezado. Este archivo de encabezado es un encabezado exportado (en otras palabras, podría copiarse a algún común / incluir directamente en su base de código). Ahora ha decidido separar la implementación de las funciones miembro al archivo .cpp. Después de separar / mover la implementación a .cpp, el archivo de encabezado ahora solo tiene los prototipos de las funciones miembro dentro de la clase. Después de los cambios anteriores, si construye su base de código puede recibir el error "referencia indefinida a 'vtable ...".

Para solucionar esto, antes de compilar, asegúrese de eliminar el archivo de encabezado (al que realizó los cambios) en el directorio common / include. También asegúrese de cambiar su archivo MAKE para acomodar / agregar el nuevo archivo .o creado a partir del nuevo archivo .cpp que acaba de crear. Cuando realice estos pasos, el compilador / enlazador ya no se quejará.

Sidharth Middela
fuente
extraño. Si se va a copiar un encabezado en otro lugar, el sistema de compilación debe actualizar la copia automáticamente tan pronto como se modifique el original, y antes de cualquier inclusión en otro archivo. Si tienes que hacerlo manualmente, estás jodido.
Offirmo
1

Obtuve este tipo de error en situaciones en las que intentaba vincularme a un objeto cuando recibí un error que impedía que el objeto se agregara al archivo.

Digamos que tengo libXYZ.a que se supone que tiene bioseq.o en int pero no lo tiene.

Recibí un error:

combineseq.cpp:(.text+0xabc): undefined reference to `vtable for bioseq'

Esto es bastante diferente de todo lo anterior. Llamaría a este objeto faltante en el problema de archivo.

Kemin Zhou
fuente
0

También es posible que recibas un mensaje como

SomeClassToTest.host.o: In function `class1::class1(std::string const&)':
class1.hpp:114: undefined reference to `vtable for class1'
SomeClassToTest.host.o: In function `class1::~class1()':
class1.hpp:119: undefined reference to `vtable for class1'
collect2: error: ld returned 1 exit status
[link] FAILED: 'g++' '-o' 'stage/tests/SomeClassToTest' 'object/tests/SomeClassToTest.host.o' 'object/tests/FakeClass1.SomeClassToTest.host.o'

si olvida definir una función virtual de una clase FakeClass1 cuando intenta vincular una prueba unitaria para otra clase SomeClass.

//class declaration in class1.h
class class1
{
    public:
    class1()
    {
    }
    virtual ~class1()
    {
    }
    virtual void ForgottenFunc();
};

Y

//class definition in FakeClass1.h
//...
//void ForgottenFunc() {} is missing here

En este caso, le sugiero que revise su falso para la clase 1 una vez más. Probablemente descubrirá que puede haber olvidado definir una función virtual ForgottenFuncen su clase falsa.

Yuriy
fuente
0

Recibí este error cuando agregué una segunda clase a un par fuente / encabezado existente. Dos encabezados de clase en el mismo archivo .h, y definiciones de función para dos clases en el mismo archivo .cpp.

He hecho esto con éxito antes, con clases destinadas a trabajar juntas, pero aparentemente esta vez no me gustó. Todavía no sé qué, pero dividirlos en una clase por unidad de compilación lo arregló de inmediato.


El intento fallido:

_gui_icondata.h:

#ifndef ICONDATA_H
#define ICONDATA_H

class Data;
class QPixmap;

class IconData
{
public:
    explicit IconData();
    virtual ~IconData();

    virtual void setData(Data* newData);
    Data* getData() const;
    virtual const QPixmap* getPixmap() const = 0;

    void toggleSelected();
    void toggleMirror();
    virtual void updateSelection() = 0;
    virtual void updatePixmap(const QPixmap* pixmap) = 0;

protected:
    Data* myData;
};

//--------------------------------------------------------------------------------------------------

#include "_gui_icon.h"

class IconWithData : public Icon, public IconData
{
    Q_OBJECT
public:
    explicit IconWithData(QWidget* parent);
    virtual ~IconWithData();

    virtual const QPixmap* getPixmap() const;
    virtual void updateSelection();
    virtual void updatePixmap(const QPixmap* pixmap);

signals:

public slots:
};

#endif // ICONDATA_H

_gui_icondata.cpp:

#include "_gui_icondata.h"

#include "data.h"

IconData::IconData()
{
    myData = 0;
}

IconData::~IconData()
{
    if(myData)
    {
        myData->removeIcon(this);
    }
    //don't need to clean up any more; this entire object is going away anyway
}

void IconData::setData(Data* newData)
{
    if(myData)
    {
        myData->removeIcon(this);
    }
    myData = newData;
    if(myData)
    {
        myData->addIcon(this, false);
    }
    updateSelection();
}

Data* IconData::getData() const
{
    return myData;
}

void IconData::toggleSelected()
{
    if(!myData)
    {
        return;
    }

    myData->setSelected(!myData->getSelected());
    updateSelection();
}

void IconData::toggleMirror()
{
    if(!myData)
    {
        return;
    }

    myData->setMirrored(!myData->getMirrored());
    updateSelection();
}

//--------------------------------------------------------------------------------------------------

IconWithData::IconWithData(QWidget* parent) :
    Icon(parent), IconData()
{
}

IconWithData::~IconWithData()
{
}

const QPixmap* IconWithData::getPixmap() const
{
    return Icon::pixmap();
}

void IconWithData::updateSelection()
{
}

void IconWithData::updatePixmap(const QPixmap* pixmap)
{
    Icon::setPixmap(pixmap, true, true);
}

Nuevamente, agregar un nuevo par fuente / encabezado y cortar / pegar la clase IconWithData textualmente "simplemente funcionó".

AaronD
fuente
0

Mi caso fue tonto, tuve un extra "después #includepor error y ¿adivina qué?

undefined reference to vtable!

He estado rascándome la cabeza y la cara palmeando durante horas comentando funciones virtuales para ver si algo cambió, y finalmente al eliminar el extra ", ¡todo se solucionó! Este tipo de cosas realmente necesitan dar como resultado un error de compilación, no un error de enlace.

Por extra ", quiero decir:

#include "SomeHeader.h""
Bahram Pouryousefi
fuente
0

En mi caso, tenía una clase base llamada Persona y dos clases derivadas llamadas Estudiante y Profesor.

Cómo se reparó mi programa fue: 1. Hice todas las funciones en la clase base Pure Virtual. 2. Usé todos los destructores virtuales comodefault ones.

Gaurav Kumar
fuente
-3

Recibí este error solo porque el nombre de un argumento de constructor difería en el archivo de encabezado y en el archivo de implementación. La firma del constructor es

PointSet (const PointSet & pset, Parent * parent = 0);

y lo que escribí en la implementación comenzó con

PointSet (const PointSet & pest, Parent * parent)

así accidentalmente reemplacé "pset" por "plaga". El compilador se quejaba de este y otros dos constructores en los que no había ningún error. Estoy usando g ++ versión 4.9.1 en Ubuntu. Y la definición de un destructor virtual en esta clase derivada no hizo ninguna diferencia (se define en la clase base). Nunca habría encontrado este error si no hubiera pegado los cuerpos de los constructores en el archivo de encabezado, definiéndolos así en clase.

vitke
fuente
77
Eso no haría ninguna diferencia, debe haber tenido el error en otro lugar y, sin darse cuenta, lo solucionó.
MM