Riferimento indefinito a vtable


357

Quando costruisco il mio programma C ++, ricevo il messaggio di errore

riferimento indefinito a 'vtable ...

Qual è la causa di questo problema? Come lo aggiusto?


Succede così che sto ricevendo l'errore per il seguente codice (la classe in questione è CGameModule.) E non riesco a capire per quale motivo sia il problema. All'inizio, ho pensato che fosse legato all'oblio di dare una funzione virtuale a un corpo, ma per quanto ho capito, tutto è tutto qui. La catena di ereditarietà è un po 'lunga, ma ecco il codice sorgente correlato. Non sono sicuro di quali altre informazioni dovrei fornire.

Nota: il costruttore è il punto in cui si sta verificando questo errore.

Il mio codice:

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;
};

Eredita da ...

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;
};

Che eredita da ....

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

Quale funzione sta gettando il "riferimento indefinito a vtable ..."?
J. Polfer,

3
Ho perso del tutto il fatto che il messaggio di errore specifica una funzione. Capita di essere il costruttore, quindi ho visto il mio nome di classe e non ho effettuato la connessione. Quindi, il costruttore sta lanciando questo. Aggiungerò quel dettaglio al mio post originale.
RyanG,

3
Se non hai ricostruito i tuoi file di progetto dopo aver apportato modifiche significative (ad es. qmake -projectE poi qmake) per generare un nuovo Makefile, questa è una probabile fonte dell'errore quando usi Qt.
David C. Rankin,

@ DavidC.Rankin, un altro problema relativo a Qt è che se il file con Q_OBJECTviene copiato esternamente, ma non fa ancora parte del file .pro, anche se si compila bene, non si collega. Dobbiamo aggiungere quel .h/.cppfile nel file .pro per poterlo fare qmake.
iammilind

Risposte:


420

Le FAQ di GCC contengono una voce:

La soluzione è garantire che siano definiti tutti i metodi virtuali non puri. Si noti che un distruttore deve essere definito anche se è dichiarato puro-virtuale [class.dtor] / 7.


17
nm -C CGameModule.o | grep CGameModule::elencherà i metodi definiti, supponendo che l'intera implementazione della classe vada nel file dell'oggetto logico. Puoi confrontarlo con ciò che è definito come virtuale per capire cosa ti sei perso.
Troy Daniels,

133
FFS, perché il compilatore non lo controlla e non stampa un messaggio di errore?
Lenar Hoyt,

20
Ovviamente, questo può essere scoperto solo dal linker, non dal compilatore.
Xoph,

2
Nel mio caso avevamo una classe astratta che non aveva un'implementazione distruttiva. Ho dovuto mettere l'implementazione vuota ~ MyClass () {}
Shefy Gur-ary,

1
È possibile ottenere un errore del genere quando nell'archivio mancano oggetti che si sta tentando di collegare (file libxyz.a): riferimento indefinito a `vtable for objfilename '
Kemin Zhou

162

Per quello che vale, dimenticare un corpo su un distruttore virtuale genera quanto segue:

riferimento indefinito a `vtable for CYourClass '.

Sto aggiungendo una nota perché il messaggio di errore è ingannevole. (Questo era con gcc versione 4.6.3.)


23
Ho dovuto mettere esplicitamente il corpo del mio distruttore virtuale vuoto nel file di definizione (* .cc). Avendolo nell'intestazione mi ha ancora dato l'errore.
PopcornKing

4
Nota che una volta ho aggiunto il distruttore virtuale al file di implementazione, gcc mi ha detto l'errore reale, che era un corpo mancante su un'altra funzione.
moodboom

1
@PopcornKing Ho visto lo stesso problema. Anche la definizione ~Destructor = default;nel file di intestazione non ha aiutato. Esiste un bug documentato nei confronti di gcc?
RD

questo potrebbe essere un problema diverso, ma il mio problema non era proprio l'implementazione per un distruttore non virtuale (stava passando a puntatori univoci / condivisi e lo rimuoveva dal file di origine, ma non aveva una "implementazione" nell'intestazione )
svenevs,

questo ha risolto il mio problema, l'aggiunta di un corpo {} vuoto per il distruttore virtuale ha evitato l'errore.
Bogdan Ionitza,

56

Quindi, ho capito il problema ed era una combinazione di cattiva logica e di non avere totale familiarità con il mondo degli automake / autotools. Stavo aggiungendo i file corretti al mio modello Makefile.am, ma non ero sicuro di quale passo del nostro processo di compilazione avesse effettivamente creato il makefile stesso. Quindi, stavo compilando un vecchio makefile che non aveva idea dei miei nuovi file.

Grazie per le risposte e il link alle FAQ di GCC. Sarò sicuro di leggerlo per evitare che questo problema si verifichi per una vera ragione.


43
In breve: il .cpp non era incluso nella build. Il messaggio di errore è davvero fuorviante.
Offirmo

67
Per gli utenti di Qt: è possibile ottenere lo stesso errore se si dimentica di creare un'intestazione.
Chris Morlier,

8
Penso che dovresti accettare la risposta di Alexandre Hamez. Le persone alla ricerca di questo errore avrebbero probabilmente bisogno della sua soluzione anziché della tua.
Tim

13
-1 Questa potrebbe essere la soluzione al tuo problema, ma non è una risposta alla domanda originale. La risposta corretta è semplicemente che non è stato fornito un file oggetto con i simboli richiesti. Perché non hai fornito loro è un'altra storia.
Walter,

12
@Walter: In realtà questa era la risposta esatta che stavo cercando. Gli altri sono ovvi e quindi inutili.
Edgar Bonet,

50

Se stai usando Qt, prova a rieseguire qmake. Se questo errore si trova nella classe del widget, qmake potrebbe non aver notato che la classe UI vtable deve essere rigenerata. Ciò ha risolto il problema per me.


2
Ho appena eliminato l'intera cartella con build ha funzionato pure.
Tomáš Zato - Ripristina Monica il

Questo non è unico qmake, ho avuto lo stesso con cmake. Parte del problema potrebbe essere che entrambi gli strumenti hanno un po 'di problemi con i file di intestazione, che potrebbero non innescare sempre una ricostruzione quando necessario.
MSalters il

2
Pensato che "Ricostruisci" reran qmake automaticamente ... apparentemente no. Ho fatto "Esegui qmake" su tuo suggerimento, quindi "Ricostruisci" e il problema è stato risolto.
yano,

45

Un riferimento indefinito a vtable può verificarsi anche a causa della seguente situazione. Prova questo:

La classe A contiene:

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

La classe B contiene:

  1. La definizione per la funzione precedente A.
  2. La definizione per la funzione precedente B.

La classe C contiene: Ora stai scrivendo una classe C in cui la otterrai dalla classe A.

Ora se provi a compilare otterrai un riferimento indefinito a vtable per la Classe C come errore.

Motivo:

functionA è definito come virtuale puro e la sua definizione è fornita nella classe B. functionB è definita come virtuale (NON PURA VIRTUALE), quindi cerca di trovare la sua definizione nella stessa classe A ma tu hai fornito la sua definizione nella classe B.

Soluzione:

  1. Rendi la funzione B come pura virtuale (se hai requisiti del genere) virtual void functionB(parameters) =0; (Funziona, è Testata)
  2. Fornire la definizione per la funzione B nella stessa Classe A mantenendola virtuale. (Spero che funzioni come non ho provato questo)

@ ilya1725 La modifica suggerita non consiste solo nel correggere la formattazione e simili, ma stai anche cambiando la risposta, ad esempio dicendo che la classe C deriva da B anziché da A e stai cambiando la seconda soluzione. Questo cambia sostanzialmente la risposta. In questi casi, si prega di lasciare un commento all'autore. Grazie!
Fabio dice Reintegrare Monica il

@FabioTurati da quale classe eredita classC allora? La frase non è chiara Inoltre, qual è il significato di "Classe C Contiene:"?
ilya1725,

@ ilya1725 Questa risposta non è molto chiara e non sono contrario a modificarla e migliorarla. Quello che sto dicendo è che la tua modifica cambia il significato della risposta, e questo è un cambiamento troppo drastico. Speriamo che l'autore intervenga e chiarisca cosa intendesse (anche se è stato inattivo per molto tempo).
Fabio dice Reintegrare Monica il

Grazie! Nel mio caso, avevo solo 2 classi nella mia gerarchia. La classe A ha dichiarato un metodo virtuale puro. La dichiarazione di Classe B affermava che avrebbe ignorato questo metodo, ma non avevo ancora scritto la definizione del metodo di sostituzione.
Nick Desaulniers,

44

Ho semplicemente ricevuto questo errore perché il mio file cpp non era nel makefile.


In effetti, sembra che il messaggio cambi leggermente rispetto al solito undefined reference to {function/class/struct}quando ci sono delle virtualcose in gioco. Mi ha buttato via.
Keith M,

30

Che cos'è un vtable?

Potrebbe essere utile sapere di cosa parla il messaggio di errore prima di provare a risolverlo. Inizierò ad un livello elevato, quindi passerò a qualche dettaglio in più. In questo modo le persone possono saltare una volta che si sentono a proprio agio con la loro comprensione dei Vtables. ... e ci sono un sacco di persone che saltano avanti proprio ora. :) Per quelli che restano in giro:

Una vtable è sostanzialmente l'implementazione più comune del polimorfismo in C ++ . Quando vengono usate vtables, ogni classe polimorfica ha una vtable da qualche parte nel programma; puoi pensarlo come un staticmembro di dati (nascosto) della classe. Ogni oggetto di una classe polimorfica è associato alla vtable per la sua classe più derivata. Controllando questa associazione, il programma può operare la sua magia polimorfica. Avvertenza importante: una vtable è un dettaglio di implementazione. Non è richiesto dallo standard C ++, anche se la maggior parte dei compilatori C ++ (tutti?) Usano vtables per implementare il comportamento polimorfico. I dettagli che presento sono approcci tipici o ragionevoli. I compilatori possono deviare da questo!

Ogni oggetto polimorfico ha un puntatore (nascosto) sulla vtable per la classe più derivata dell'oggetto (possibilmente più puntatori, nei casi più complessi). Guardando il puntatore, il programma può dire qual è il tipo "reale" di un oggetto (tranne durante la costruzione, ma saltiamo quel caso speciale). Ad esempio, se un oggetto di tipo Anon punta alla vtable di A, allora quell'oggetto è in realtà un oggetto secondario di qualcosa derivato A.

Il nome "vtable" deriva da " v funzione irtual tavolo ". È una tabella che memorizza i puntatori a funzioni (virtuali). Un compilatore sceglie la sua convenzione per come è disposta la tabella; un approccio semplice è quello di passare attraverso le funzioni virtuali nell'ordine in cui sono dichiarate all'interno delle definizioni di classe. Quando viene chiamata una funzione virtuale, il programma segue il puntatore dell'oggetto su una vtable, passa alla voce associata alla funzione desiderata, quindi utilizza il puntatore della funzione memorizzata per invocare la funzione corretta. Ci sono vari trucchi per farlo funzionare, ma non entrerò in quelli qui.

Dove / quando viene vtablegenerato?

Una vtable viene generata automaticamente (a volte chiamata "emessa") dal compilatore. Un compilatore potrebbe emettere una vtable in ogni unità di traduzione che vede una definizione di classe polimorfica, ma che di solito sarebbe inutile eccessivo. Un'alternativa ( usata da gcc , e probabilmente da altri) è quella di scegliere una singola unità di traduzione in cui posizionare la vtable, in modo simile a come si selezionerebbe un singolo file sorgente in cui inserire i membri di dati statici di una classe. Se questo processo di selezione non riesce a scegliere alcuna unità di traduzione, la vtable diventa un riferimento indefinito. Da qui l'errore, il cui messaggio è certamente non particolarmente chiaro.

Allo stesso modo, se il processo di selezione sceglie un'unità di traduzione, ma quel file oggetto non viene fornito al linker, allora la vtable diventa un riferimento indefinito. Sfortunatamente, il messaggio di errore può essere ancora meno chiaro in questo caso rispetto al caso in cui il processo di selezione non è riuscito. (Grazie ai rispondenti che hanno menzionato questa possibilità. Probabilmente l'avrei dimenticata altrimenti.)

Il processo di selezione usato da gcc ha senso se iniziamo con la tradizione di dedicare un (singolo) file sorgente a ciascuna classe che ne ha bisogno per la sua implementazione. Sarebbe bello emettere la vtable durante la compilazione del file sorgente. Chiamiamo questo il nostro obiettivo. Tuttavia, il processo di selezione deve funzionare anche se questa tradizione non viene seguita. Quindi, invece di cercare l'implementazione dell'intera classe, cerchiamo l'implementazione di un membro specifico della classe. Se viene seguita la tradizione - e se quel membro è effettivamente implementato - allora questo raggiunge l'obiettivo.

Il membro selezionato da gcc (e potenzialmente da altri compilatori) è la prima funzione virtuale non in linea che non è pura virtuale. Se fai parte della folla che dichiara costruttori e distruttori prima che funzioni altri membri, allora quel distruttore ha buone probabilità di essere selezionato. (Ti sei ricordato di rendere virtuale il distruttore, vero?) Ci sono eccezioni; Mi aspetto che le eccezioni più comuni siano quando viene fornita una definizione inline per il distruttore e quando viene richiesto il distruttore predefinito (usando " = default").

L'astuto potrebbe notare che una classe polimorfica è autorizzata a fornire definizioni in linea per tutte le sue funzioni virtuali. Ciò non causa il fallimento del processo di selezione? Lo fa nei compilatori più vecchi. Ho letto che gli ultimi compilatori hanno affrontato questa situazione, ma non conosco i numeri di versione pertinenti. Potrei provare a cercarlo, ma è più facile scrivere il codice o attendere che il compilatore si lamenti.

In breve, ci sono tre cause principali dell'errore "riferimento indefinito a vtable":

  1. A una funzione membro manca la sua definizione.
  2. Un file oggetto non viene collegato.
  3. Tutte le funzioni virtuali hanno definizioni incorporate.

Queste cause sono di per sé insufficienti a causare l'errore da sole. Piuttosto, questi sono ciò che vorresti affrontare per risolvere l'errore. Non aspettarti che la creazione intenzionale di una di queste situazioni produrrà sicuramente questo errore; ci sono altri requisiti. Aspettatevi che la risoluzione di queste situazioni risolva questo errore.

(OK, il numero 3 potrebbe essere stato sufficiente quando è stata posta questa domanda.)

Come correggere l'errore?

Bentornati gente che salta avanti! :)

  1. Guarda la definizione della tua classe. Trova la prima funzione virtuale non in linea che non è pura virtuale (non " = 0") e di cui fornisci la definizione (non " = default").
    • Se non esiste tale funzione, prova a modificare la tua classe in modo che ce ne sia una. (Errore eventualmente risolto.)
    • Vedi anche la risposta di Philip Thomas per un avvertimento.
  2. Trova la definizione per quella funzione. Se manca, aggiungilo! (Errore eventualmente risolto.)
  3. Controlla il tuo comando di collegamento. Se non menziona il file oggetto con la definizione di quella funzione, correggilo! (Errore eventualmente risolto.)
  4. Ripetere i passaggi 2 e 3 per ciascuna funzione virtuale, quindi per ciascuna funzione non virtuale, fino alla risoluzione dell'errore. Se sei ancora bloccato, ripeti l'operazione per ciascun membro di dati statici.

Esempio
I dettagli di cosa fare possono variare e talvolta si ramificano in domande separate (come Cosa è un riferimento indefinito / errore di simbolo esterno non risolto e come posso risolverlo? ). Fornirò tuttavia un esempio di cosa fare in un caso specifico che potrebbe confondere i programmatori più recenti.

Il passaggio 1 menziona la modifica della classe in modo che abbia una funzione di un certo tipo. Se la descrizione di quella funzione ti passasse per la testa, potresti trovarti nella situazione che intendo affrontare. Tieni presente che questo è un modo per raggiungere l'obiettivo; non è l'unico modo e ci potrebbero essere facilmente modi migliori nella tua situazione specifica. Chiamiamo la tua classe A. Il distruttore è dichiarato (nella definizione di classe) come uno dei due

virtual ~A() = default;

o

virtual ~A() {}

? In tal caso, due passaggi trasformeranno il distruttore nel tipo di funzione che desideriamo. Innanzitutto, cambia quella linea in

virtual ~A();

In secondo luogo, inserisci la seguente riga in un file sorgente che fa parte del tuo progetto (preferibilmente il file con l'implementazione della classe, se ne hai uno):

A::~A() {}

Ciò rende il tuo distruttore (virtuale) non in linea e non generato dal compilatore. (Sentiti libero di modificare le cose per adattarle meglio al tuo stile di formattazione del codice, come aggiungere un commento di intestazione alla definizione della funzione.)


Oh, bravo! Per la spiegazione molto dettagliata e molto mirata.
David C. Rankin,

Ho dovuto scorrere fino a lontano per leggere questo. Avere un voto per l'eccellente spiegazione!
Thomas,

24

C'è molta speculazione in corso in varie risposte qui. Di seguito fornirò un codice abbastanza minimale che riproduce questo errore e spiegherà perché si sta verificando.

Codice abbastanza minimale per riprodurre questo errore

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;
}

Puoi compilarlo usando GCC in questo modo:

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

Ora puoi riprodurre l'errore rimuovendolo = 0in IBase.hpp. Ottengo questo errore:

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

Spiegazione

Si noti che il codice sopra riportato non richiede alcun distruttore virtuale, costruttore o altri file aggiuntivi per la compilazione per avere esito positivo (anche se è necessario averli).

Il modo per capire questo errore è il seguente: Linker sta cercando il costruttore di IBase. Ne avrà bisogno per il costruttore di Derived. Tuttavia, poiché Derivato ignora i metodi da IBase, ha una tabella collegata ad esso che farà riferimento a IBase. Quando il linker dice "riferimento indefinito a vtable per IBase" significa sostanzialmente che Derived ha un riferimento vtable a IBase ma non riesce a trovare alcun codice oggetto compilato di IBase da cercare. Quindi la linea di fondo è che la classe IBase ha dichiarazioni senza implementazioni. Ciò significa che un metodo in IBase è dichiarato virtuale ma abbiamo dimenticato di contrassegnarlo come puro virtuale O di fornire la sua definizione.

Suggerimento per la separazione

Se tutto il resto fallisce, un modo per eseguire il debug di questo errore è creare un programma minimo che si compili e quindi continuare a cambiarlo in modo che arrivi allo stato desiderato. Nel mezzo, continua a compilare per vedere quando inizia a fallire.

Nota sul sistema di build ROS e Catkin

Se stavi compilando sopra l'insieme di classi in ROS usando il sistema di generazione catkin, allora avrai bisogno delle seguenti righe in CMakeLists.txt:

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

La prima riga dice sostanzialmente che vogliamo creare un eseguibile chiamato myclass e il codice per costruire questo può essere trovato nei file che seguono. Uno di questi file dovrebbe avere main (). Si noti che non è necessario specificare i file .hpp da nessuna parte in CMakeLists.txt. Inoltre non è necessario specificare Derived.cpp come libreria.


18

Ho appena incontrato un'altra causa per questo errore che puoi verificare.

La classe base ha definito una pura funzione virtuale come:

virtual int foo(int x = 0);

E la sottoclasse aveva

int foo(int x) override;

Il problema era il refuso che "=0"si supponeva fosse al di fuori della parentesi:

virtual int foo(int x) = 0;

Quindi, nel caso tu stia scorrendo così in basso, probabilmente non hai trovato la risposta - questo è qualcos'altro da controllare.


12

Questo può accadere abbastanza facilmente se si dimentica di collegarsi al file oggetto che ha la definizione.


1
Aggiungi qualche altra descrizione alla tua risposta e possibile correzione.
Mohit Jain,

1
Dimenticare di collegare può includere dimenticare di aggiungere alle istruzioni di costruzione. Nel mio caso, il mio file cpp aveva tutto perfettamente "definito" tranne che avevo dimenticato di aggiungere il file cpp all'elenco dei sorgenti (nel mio CMakeLists.txt, ma lo stesso può accadere in altri sistemi di compilazione come in un file .pro). Di conseguenza, tutto è stato compilato e quindi ho ricevuto l'errore al momento del collegamento ...
saggio

@Mohit Jain Il modo in cui collegare i file oggetto dipende dalla configurazione e dagli strumenti dell'ambiente. Sfortunatamente una correzione specifica per una persona potrebbe essere diversa per un'altra (ad es. CMake vs strumento proprietario vs IDE vs ecc.)
Hazok

11

Il compilatore GNU C ++ deve prendere una decisione su dove mettere vtablenel caso in cui si abbia la definizione delle funzioni virtuali di un oggetto distribuite su più unità di compilazione (ad esempio alcune definizioni delle funzioni virtuali degli oggetti sono in un file .cpp altre in un altro. file cpp e così via).

Il compilatore sceglie di inserire il file vtablenello stesso posto in cui è definita la prima funzione virtuale dichiarata.

Ora, se per qualche motivo hai dimenticato di fornire una definizione per quella prima funzione virtuale dichiarata nell'oggetto (o hai erroneamente dimenticato di aggiungere l'oggetto compilato nella fase di collegamento), otterrai questo errore.

Come effetto collaterale, tieni presente che solo per questa particolare funzione virtuale non otterrai il tradizionale errore del linker come ti manca la funzione pippo .


8

Non per attraversare posta ma. Se hai a che fare con l' eredità, il secondo successo di Google è stato quello che mi ero perso, vale a dire. tutti i metodi virtuali dovrebbero essere definiti.

Ad esempio:

virtual void fooBar() = 0;

Per ulteriori dettagli, consultare la sezione C ++ Undefined Reference a vtable ed eredity . Ho appena realizzato che è già stato menzionato sopra, ma diamine potrebbe aiutare qualcuno.


8

Ok, la soluzione è che potresti aver perso la definizione. Vedere l'esempio seguente per evitare l'errore del compilatore 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
{

}

7
  • Sei sicuro che CDasherComponentabbia un corpo per il distruttore? Sicuramente non è qui: la domanda è se si trova nel file .cc.
  • Dal punto di vista dello stile, CDasherModuledovrebbe definire esplicitamente il suo distruttore virtual.
  • Sembra che CGameModuleabbia un extra } alla fine (dopo il}; // for the class ).
  • Viene CGameModulecollegato alle librerie che definiscono CDasherModulee CDasherComponent?

- Sì, CDasherComponent ha un corpo distruttore nel cpp. Ho pensato che fosse dichiarato nel .h quando l'ho pubblicato. - Debitamente annotato. - Questa è stata una parentesi aggiuntiva che ho aggiunto per errore quando ho rimosso la documentazione. - Per quanto ho capito, sì. Ho modificato un file automake che non ho scritto, ma ho seguito i modelli che hanno funzionato per altre classi con lo stesso modello di eredità dalle stesse classi, quindi a meno che non abbia fatto uno stupido errore (Totalmente possibile) , Non penso che sia così.
RyanG,

@RyanG: prova a spostare tutte le definizioni delle funzioni virtuali nella definizione della classe. Assicurati che siano tutti lì e vedi se il risultato cambia.
Stephen,

5

Forse la mancanza del distruttore virtuale sta contribuendo?

virtual ~CDasherModule(){};

5

Questo è stato il primo risultato di ricerca per me, quindi ho pensato di aggiungere un'altra cosa da verificare: assicurarsi che la definizione di funzioni virtuali sia effettivamente nella classe. Nel mio caso, ho avuto questo:

File di intestazione:

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

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

e nel mio file .cc:

void foo() {
  ...
}

Questo dovrebbe leggere

void B::foo() {
}


3

Tante risposte qui, ma nessuna di queste sembrava aver trattato il mio problema. Ho avuto il seguente:


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

E in un altro file (incluso nella compilazione e nel collegamento, ovviamente)

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

Bene, questo non ha funzionato e ho avuto l'errore di cui tutti parlano. Per risolverlo, ho dovuto spostare la definizione effettiva di Foo dalla dichiarazione di classe in quanto tale:

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

C::Foo(){
   //bar
}

Non sono un guru del C ++, quindi non posso spiegare perché questo sia più corretto, ma ha risolto il problema per me.


Non sono un guru del C ++, ma sembra essere correlato al mescolare la dichiarazione e la definizione nello stesso file, con un file di definizione aggiuntivo.
Terry G Lorber,

1
Quando la definizione della funzione era all'interno della definizione della classe, veniva implicitamente dichiarata "in linea". A quel punto, tutte le tue funzioni virtuali sono state integrate. Quando la funzione è stata spostata al di fuori della definizione di classe, non era più una funzione "incorporata". Dato che avevi una funzione virtuale non incorporata, il tuo compilatore sapeva dove emettere la vtable. (Una spiegazione più approfondita non si adatta a un commento.)
JaMiT

3

Quindi stavo usando Qt con Windows XP e il compilatore MinGW e questa cosa mi stava facendo impazzire.

Fondamentalmente il moc_xxx.cpp è stato generato vuoto anche quando sono stato aggiunto

Q_OBJECT

Eliminare tutto ciò che rende le funzioni virtuali, esplicite e qualunque cosa tu pensi non abbia funzionato. Alla fine ho iniziato a rimuovere riga per riga e ho scoperto che avevo

#ifdef something

Intorno al file. Anche quando il file #ifdef era vero non veniva generato il file moc.

Quindi la rimozione di tutti i #ifdefs ha risolto il problema.

Questa cosa non stava succedendo con Windows e VS 2013.


Commentare la riga Q_OBJECT ha reso la mia semplice app di test compilata in modo chiaro g++ *.cpp .... (Avevo bisogno di qualcosa di veloce e sporco, ma Qmake era pieno di dolore.)
Nathan Kidd,

2

Se tutto il resto fallisce, cerca la duplicazione. Sono stato indirizzato erroneamente dal riferimento esplicito iniziale a costruttori e distruttori fino a quando non ho letto un riferimento in un altro post. È un metodo irrisolto. Nel mio caso, ho pensato di aver sostituito la dichiarazione che utilizzava char * xml come parametro con uno che utilizzava il const char * xml inutilmente problematico, ma invece, ne avevo creato uno nuovo e ne avevo lasciato l'altro in posizione.


2

Ci sono molte possibilità menzionate per causare questo errore e sono sicuro che molte di esse causano l'errore. Nel mio caso, c'era un'altra definizione della stessa classe, a causa di una duplicazione del file di origine. Questo file è stato compilato, ma non collegato, quindi il linker si lamentava di non riuscire a trovarlo.

Per riassumere, direi che se hai fissato la lezione abbastanza a lungo e non riesci a vedere quale possibile problema di sintassi potrebbe causarla, cerca problemi di compilazione come un file mancante o un file duplicato.


2

Nel mio caso sto usando Qt e avevo definito una QObjectsottoclasse in un foo.cpp(non .h) file. La correzione era aggiungere #include "foo.moc"alla fine di foo.cpp.


2

Penso che valga anche la pena ricordare che riceverai anche il messaggio quando provi a collegarti a un oggetto di qualsiasi classe che ha almeno un metodo virtuale e il linker non riesce a trovare il file. Per esempio:

Foo.hpp:

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

Foo.cpp:

#include "Foo.hpp"

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

Compilato con:

g++ Foo.cpp -c

E main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Compilato e collegato con:

g++ main.cpp -o main

Fornisce il nostro errore preferito:

/tmp/cclKnW0g.o: nella funzione main': main.cpp:(.text+0x1a): undefined reference tovtable per Foo 'collect2: errore: ld ha restituito 1 stato di uscita

Ciò si è verificato dal mio indecente perché:

  1. Vtable viene creato per classe al momento della compilazione

  2. Linker non ha accesso a vtable che si trova in Foo.o


1

Ho ricevuto questo errore nel seguente scenario

Considerare un caso in cui è stata definita l'implementazione delle funzioni membro di una classe nel file di intestazione stesso. Questo file di intestazione è un'intestazione esportata (in altre parole, potrebbe essere copiata in alcuni comuni / includi direttamente nella tua base di codice). Ora hai deciso di separare l'implementazione delle funzioni membro nel file .cpp. Dopo aver separato / spostato l'implementazione in .cpp, il file di intestazione ora include solo i prototipi delle funzioni membro all'interno della classe. Dopo le modifiche precedenti, se si crea la base di codice, è possibile che venga visualizzato l'errore "riferimento indefinito a 'vtable ...".

Per risolvere questo problema, prima di creare, assicurati di eliminare il file di intestazione (a cui hai apportato le modifiche) nella directory common / include. Assicurati anche di modificare il tuo makefile in modo da accogliere / aggiungere il nuovo file .o creato dal nuovo file .cpp appena creato. Quando esegui questi passaggi, il compilatore / linker non si lamenterà più.


strano. Se un'intestazione deve essere copiata da qualche altra parte, il sistema di compilazione dovrebbe aggiornare la copia automaticamente non appena l'originale viene modificato e prima di qualsiasi inclusione in un altro file. Se devi farlo manualmente sei fregato.
Offirmo

1

Ho avuto questo tipo di errore in situazioni in cui stavo cercando di collegarmi a un oggetto quando ho avuto un bug di make che impediva all'oggetto di essere aggiunto all'archivio.

Supponiamo che io abbia libXYZ.a che dovrebbe avere bioseq.o in int ma non è così.

Ho ricevuto un errore:

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

Questo è completamente diverso da tutto quanto sopra. Definirei questo oggetto mancante nel problema di archiviazione.


0

È anche possibile che tu riceva un messaggio simile

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'

se si dimentica di definire una funzione virtuale di una classe FakeClass1 quando si sta tentando di collegare un unit test per un'altra classe SomeClass.

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

E

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

In questo caso ti suggerisco di controllare di nuovo il tuo falso per class1. Probabilmente scoprirai che potresti aver dimenticato di definire una funzione virtuale ForgottenFuncnella tua falsa classe.


0

Ho ricevuto questo errore quando ho aggiunto una seconda classe a una coppia sorgente / intestazione esistente. Due intestazioni di classe nello stesso file .h e definizioni di funzione per due classi nello stesso file .cpp.

L'ho già fatto con successo in precedenza, con lezioni pensate per lavorare a stretto contatto, ma a quanto pare questa volta non mi piaceva qualcosa. Ancora non so cosa, ma suddividendoli in una classe per unità di compilazione è stato risolto.


Il tentativo fallito:

_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);
}

Ancora una volta, aggiungere una nuova coppia sorgente / intestazione e tagliare / incollare la classe IconWithData alla lettera "ha funzionato".


0

Il mio caso è stato uno sciocco, ho avuto un extra "dopo #includeper errore e indovina un po '?

undefined reference to vtable!

Ho grattato la testa e il viso per ore, commentando le funzioni virtuali per vedere se qualcosa è cambiato, e infine rimuovendo il extra ", tutto è stato risolto! Questo tipo di cose deve davvero comportare un errore di compilazione e non un errore di collegamento.

Per extra ", intendo:

#include "SomeHeader.h""

0

Nel mio caso, avevo una classe base chiamata Person e due classi derivate denominate Student e Professor.

Il modo in cui il mio programma è stato risolto è stato: 1. Ho fatto tutte le funzioni nella classe base Pure Virtual. 2. Ho usato tutti i distruttori virtuali comedefault ones.


-3

Ho ricevuto questo errore solo perché il nome di un argomento del costruttore differiva nel file di intestazione e nel file di implementazione. La firma del costruttore è

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

e quello che ho scritto nell'implementazione è iniziato con

PointSet (const PointSet & pest, Parent * parent)

così ho accidentalmente sostituito "pset" con "pest". Il compilatore si lamentava di questo e di altri due costruttori in cui non vi era alcun errore. Sto usando g ++ versione 4.9.1 sotto Ubuntu. E definire un distruttore virtuale in questa classe derivata non ha fatto alcuna differenza (è definito nella classe base). Non avrei mai trovato questo errore se non avessi incollato i corpi dei costruttori nel file header, definendoli così in classe.


7
Ciò non farebbe alcuna differenza, devi aver avuto l'errore altrove e averlo risolto inavvertitamente.
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.