Référence non définie à vtable

357

Lors de la construction de mon programme C ++, je reçois le message d'erreur

référence non définie à «vtable ...

Quelle est la cause de ce problème? Comment je le répare?


Il se trouve que j'obtiens l'erreur pour le code suivant (la classe en question est CGameModule.) Et je ne peux pas pour la vie de moi comprendre quel est le problème. Au début, je pensais que c'était lié à l'oubli de donner un corps à une fonction virtuelle, mais d'après ce que je comprends, tout est là. La chaîne d'héritage est un peu longue, mais voici le code source associé. Je ne sais pas quelles autres informations je dois fournir.

Remarque: Le constructeur est l'endroit où cette erreur se produit, semble-t-il.

Mon code:

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

Hérite 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;
};

Qui hérite 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
la source
Quelle fonction lance la "référence non définie à vtable ..."?
J.Polfer
3
J'ai totalement manqué que le message d'erreur spécifie une fonction. Il se trouve que c'est le constructeur, donc j'ai vu mon nom de classe et je n'ai pas fait la connexion. Ainsi, le constructeur lance ceci. Je vais ajouter ce détail à mon message d'origine.
RyanG
3
Si vous n'avez pas reconstruit vos fichiers de projet après avoir apporté des modifications importantes (par exemple qmake -project, puis qmake) pour générer un nouveau Makefile, c'est probablement une source d'erreur lors de l'utilisation de Qt.
David C. Rankin
@ DavidC.Rankin, un autre problème lié à Qt est que si le fichier avec Q_OBJECTest copié en externe, mais ne fait pas encore partie du fichier .pro, bien qu'il compile bien, il ne se lie pas. Nous devons ajouter ce .h/.cppfichier dans le fichier .pro pour pouvoir qmake.
iammilind

Réponses:

420

La FAQ du CCG contient une entrée:

La solution est de s'assurer que toutes les méthodes virtuelles qui ne sont pas pures sont définies. Notez qu'un destructeur doit être défini même s'il est déclaré pur-virtuel [class.dtor] / 7.

Alexandre Hamez
la source
17
nm -C CGameModule.o | grep CGameModule::répertorie les méthodes définies, en supposant que l'intégralité de votre implémentation de classe va dans le fichier d'objet logique. Vous pouvez comparer cela avec ce qui est défini comme virtuel pour comprendre ce que vous avez manqué.
Troy Daniels
133
FFS, pourquoi le compilateur ne vérifie-t-il pas cela et n'imprime-t-il pas un message d'erreur?
Lenar Hoyt
20
Évidemment, cela ne peut être découvert que par l'éditeur de liens, pas par le compilateur.
Xoph
2
Dans mon cas, nous avions une classe abstraite qui n'avait pas d'implémentation de destructeur. J'ai dû mettre l'implémentation vide ~ MyClass () {}
Shefy Gur-ary
1
Vous pouvez obtenir une erreur comme celle-ci lorsque des objets que vous essayez de lier sont manquants dans l'archive (fichier libxyz.a): référence non définie à `vtable pour objfilename '
Kemin Zhou
162

Pour ce que ça vaut, oublier un corps sur un destructeur virtuel génère ce qui suit:

référence non définie à «vtable pour CYourClass».

J'ajoute une note car le message d'erreur est trompeur. (C'était avec la version 4.6.3 de gcc.)

Dan
la source
23
J'ai dû explicitement mettre le corps de mon destructeur virtuel vide dans le fichier de définition (* .cc). L'avoir dans l'en-tête m'a toujours donné l'erreur.
PopcornKing
4
Notez qu'une fois que j'ai ajouté le destructeur virtuel au fichier d'implémentation, alors gcc m'a dit l'erreur réelle, qui était un corps manquant sur une autre fonction.
moodboom
1
@PopcornKing J'ai vu le même problème. Même la définition ~Destructor = default;dans le fichier d'en-tête n'a pas aidé. Existe-t-il un bogue documenté contre gcc?
RD
cela peut être un problème différent, mais mon problème était tout simplement de ne pas avoir d'implémentation pour un destructeur non virtuel (basculait vers des pointeurs uniques / partagés et le supprimait du fichier source, mais n'avait pas d '"implémentation" dans l'en-tête )
svenevs
cela a résolu mon problème, l'ajout d'un corps vide {} pour le destructeur virtuel a évité l'erreur.
Bogdan Ionitza
56

Donc, j'ai compris le problème et c'était une combinaison de mauvaise logique et de ne pas être totalement familier avec le monde automake / autotools. J'ajoutais les fichiers corrects à mon modèle Makefile.am, mais je ne savais pas quelle étape de notre processus de création avait réellement créé le makefile lui-même. Donc, je compilais avec un vieux makefile qui n'avait aucune idée de mes nouveaux fichiers.

Merci pour les réponses et le lien vers la FAQ GCC. Je serai sûr de lire cela pour éviter que ce problème ne se produise pour une vraie raison.

RyanG
la source
43
En bref: le .cpp n'était tout simplement pas inclus dans la build. Le message d'erreur est vraiment trompeur.
Offirmo
67
Pour les utilisateurs de Qt: vous pouvez obtenir cette même erreur si vous oubliez de mocaliser un en-tête.
Chris Morlier
8
Je pense que vous devriez cependant accepter la réponse d'Alexandre Hamez. Les personnes recherchant cette erreur auraient probablement besoin de sa solution au lieu de la vôtre.
Tim
13
-1 Cela peut être la solution à votre problème, mais ce n'est pas une réponse à la question d'origine. La bonne réponse est simplement que vous n'avez pas fourni de fichier objet avec les symboles requis. Pourquoi vous ne les avez pas fournis est une autre histoire.
Walter
12
@Walter: En fait, c'était la réponse exacte que je cherchais. Les autres sont évidents et donc inutiles.
Edgar Bonet
50

Si vous utilisez Qt, essayez de relancer qmake. Si cette erreur se trouve dans la classe du widget, qmake peut ne pas avoir remarqué que la table ui de classe doit être régénérée. Cela a résolu le problème pour moi.

gluk47
la source
2
Je viens de supprimer tout le dossier avec la construction, cela a également fonctionné.
Tomáš Zato - Rétablir Monica
Ce n'est pas unique qmake, j'ai eu la même chose avec cmake. Une partie du problème pourrait être que les deux outils ont un peu un problème avec les fichiers d'en-tête, qui ne déclenchent pas toujours une reconstruction en cas de besoin.
MSalters
2
Je pensais que "Rebuild" reran qmake automatiquement ... apparemment non. J'ai fait "Exécuter qmake" à votre suggestion, puis "Reconstruire" et cela a résolu mon problème.
yano
45

Une référence non définie à vtable peut également se produire en raison de la situation suivante. Essayez ceci:

La classe A contient:

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

La classe B contient:

  1. La définition de la fonction ci-dessusA.
  2. La définition de la fonction ci-dessusB.

La classe C contient: Maintenant, vous écrivez une classe C dans laquelle vous allez la dériver de la classe A.

Maintenant, si vous essayez de compiler, vous obtiendrez une référence indéfinie à vtable pour la classe C en tant qu'erreur.

Raison:

functionA est défini comme virtuel pur et sa définition est fournie dans la classe B. functionB est défini comme virtuel (PAS PURE VIRTUEL), il essaie donc de trouver sa définition dans la classe A elle-même, mais vous avez fourni sa définition dans la classe B.

Solution:

  1. Rendre la fonction B pure virtuelle (si vous avez des exigences comme ça) virtual void functionB(parameters) =0; (cela fonctionne, il est testé)
  2. Fournissez la définition de functionB dans la classe A elle-même en la gardant virtuelle. (J'espère que cela fonctionne car je n'ai pas essayé cela)
Srinivasa Lakkaraju
la source
@ ilya1725 Votre modification proposée est non seulement fixer le formatage et autres, vous changez aussi la réponse, par exemple en disant que la classe C est dérivée de B au lieu de A, et vous changez la deuxième solution. Cela change considérablement la réponse. Dans ces cas, veuillez laisser un commentaire à l'auteur à la place. Je vous remercie!
Fabio dit Réintégrer Monica le
@FabioTurati de quelle classe hérite classC alors? La phrase n'est pas claire. En outre, quelle est la signification de «la classe C contient:»?
ilya1725
@ ilya1725 Cette réponse n'est pas très claire, et je ne suis pas contre de la modifier et de l'améliorer. Ce que je dis, c'est que votre modification change le sens de la réponse, et c'est un changement trop radical. J'espère que l'auteur interviendra et clarifiera ce qu'il voulait dire (bien qu'il soit inactif depuis longtemps).
Fabio dit Réintégrer Monica le
Merci! Dans mon cas, je n'avais que 2 classes dans ma hiérarchie. La classe A a déclaré une méthode virtuelle pure. La déclaration de classe B a déclaré qu'elle remplacerait cette méthode, mais je n'avais pas encore écrit la définition de la méthode surchargée.
Nick Desaulniers
44

J'ai simplement eu cette erreur car mon fichier cpp n'était pas dans le makefile.

Volonté
la source
En effet, il semble que le message change légèrement de l'habituel undefined reference to {function/class/struct}quand il y a des virtualchoses impliquées. Me jeta.
Keith M
30

Qu'est-ce qu'un vtable?

Il peut être utile de savoir de quoi parle le message d'erreur avant d'essayer de le corriger. Je vais commencer à un niveau élevé, puis je descendrai jusqu'à plus de détails. De cette façon, les gens peuvent avancer une fois qu'ils sont à l'aise avec leur compréhension des vtables. … Et il y a un tas de gens qui sautent en ce moment. :) Pour ceux qui restent:

Une vtable est fondamentalement l'implémentation la plus courante du polymorphisme en C ++ . Lorsque vtables est utilisé, chaque classe polymorphe a une vtable quelque part dans le programme; vous pouvez le considérer comme un staticmembre de données (caché) de la classe. Chaque objet d'une classe polymorphe est associé à la table virtuelle pour sa classe la plus dérivée. En vérifiant cette association, le programme peut travailler sa magie polymorphe. Mise en garde importante: une table virtuelle est un détail d'implémentation. Il n'est pas mandaté par la norme C ++, même si la plupart (tous?) Des compilateurs C ++ utilisent vtables pour implémenter un comportement polymorphe. Les détails que je présente sont des approches typiques ou raisonnables. Les compilateurs sont autorisés à s'en écarter!

Chaque objet polymorphe a un pointeur (caché) vers la table virtuelle pour la classe la plus dérivée de l'objet (éventuellement plusieurs pointeurs, dans les cas les plus complexes). En regardant le pointeur, le programme peut dire quel est le type "réel" d'un objet (sauf pendant la construction, mais sautons ce cas spécial). Par exemple, si un objet de type Ane pointe pas vers la table de A, cet objet est en fait un sous-objet de quelque chose dérivé deA .

Le nom "vtable" vient de " v irtual function table ". Il s'agit d'un tableau qui stocke des pointeurs vers des fonctions (virtuelles). Un compilateur choisit sa convention pour la présentation de la table; une approche simple consiste à parcourir les fonctions virtuelles dans l'ordre dans lequel elles sont déclarées dans les définitions de classe. Lorsqu'une fonction virtuelle est appelée, le programme suit le pointeur de l'objet vers une table virtuelle, accède à l'entrée associée à la fonction souhaitée, puis utilise le pointeur de fonction stocké pour appeler la fonction correcte. Il y a plusieurs astuces pour faire fonctionner cela, mais je ne vais pas entrer dans celles-ci ici.

Où / quand est vtablegénéré?

Une table virtuelle est générée automatiquement (parfois appelée "émise") par le compilateur. Un compilateur pourrait émettre une vtable dans chaque unité de traduction qui voit une définition de classe polymorphe, mais cela serait généralement inutile. Une alternative ( utilisée par gcc , et probablement par d'autres) consiste à choisir une seule unité de traduction dans laquelle placer la table virtuelle, de la même manière que vous choisiriez un seul fichier source dans lequel placer les membres de données statiques d'une classe. Si ce processus de sélection ne parvient pas à sélectionner des unités de traduction, la table virtuelle devient une référence non définie. D'où l'erreur, dont le message n'est certes pas particulièrement clair.

De même, si le processus de sélection choisit une unité de traduction, mais que ce fichier objet n'est pas fourni à l'éditeur de liens, alors la table virtuelle devient une référence non définie. Malheureusement, le message d'erreur peut être encore moins clair dans ce cas que dans le cas où le processus de sélection a échoué. (Merci aux répondeurs qui ont mentionné cette possibilité. Je l'aurais probablement oubliée autrement.)

Le processus de sélection utilisé par gcc prend tout son sens si nous partons de la tradition de consacrer un (seul) fichier source à chaque classe qui en a besoin pour sa mise en œuvre. Ce serait bien d'émettre la vtable lors de la compilation de ce fichier source. Appelons cela notre objectif. Cependant, le processus de sélection doit fonctionner même si cette tradition n'est pas respectée. Ainsi, au lieu de rechercher l'implémentation de la classe entière, recherchons l'implémentation d'un membre spécifique de la classe. Si la tradition est respectée - et si ce membre est en fait mis en œuvre - alors cela atteint l'objectif.

Le membre sélectionné par gcc (et potentiellement par d'autres compilateurs) est la première fonction virtuelle non en ligne qui n'est pas pure virtuelle. Si vous faites partie de la foule qui déclare les constructeurs et les destructeurs avant les autres fonctions membres, alors ce destructeur a de bonnes chances d'être sélectionné. (Vous vous souveniez de rendre le destructeur virtuel, non?) Il y a des exceptions; Je m'attends à ce que les exceptions les plus courantes soient lorsqu'une définition en ligne est fournie pour le destructeur et lorsque le destructeur par défaut est demandé (en utilisant " = default").

L'astucieux pourrait remarquer qu'une classe polymorphe est autorisée à fournir des définitions en ligne pour toutes ses fonctions virtuelles. Cela ne fait-il pas échouer le processus de sélection? C'est le cas dans les anciens compilateurs. J'ai lu que les derniers compilateurs ont résolu cette situation, mais je ne connais pas les numéros de version pertinents. Je pourrais essayer de chercher cela, mais il est plus facile de coder autour ou d'attendre que le compilateur se plaint.

En résumé, il existe trois causes principales de l'erreur "référence non définie à vtable":

  1. Une fonction membre manque sa définition.
  2. Un fichier objet n'est pas lié.
  3. Toutes les fonctions virtuelles ont des définitions en ligne.

Ces causes sont en elles-mêmes insuffisantes pour provoquer l'erreur par elles-mêmes. Voici plutôt ce à quoi vous vous adresseriez pour résoudre l'erreur. Ne vous attendez pas à ce que la création intentionnelle d'une de ces situations produise définitivement cette erreur; il y a d'autres exigences. Attendez-vous à ce que la résolution de ces situations résout cette erreur.

(OK, le numéro 3 aurait peut-être suffi lorsque cette question a été posée.)

Comment corriger l'erreur?

Bienvenue aux gens qui sautent! :)

  1. Regardez votre définition de classe. Recherchez la première fonction virtuelle non en ligne qui n'est pas virtuelle pure (pas " = 0") et dont vous définissez la définition (pas " = default").
    • S'il n'y a pas une telle fonction, essayez de modifier votre classe pour qu'il y en ait une. (Erreur éventuellement résolue.)
    • Voir également la réponse de Philip Thomas pour une mise en garde.
  2. Trouvez la définition de cette fonction. S'il manque, ajoutez-le! (Erreur éventuellement résolue.)
  3. Vérifiez votre commande de lien. S'il ne mentionne pas le fichier objet avec la définition de cette fonction, corrigez cela! (Erreur éventuellement résolue.)
  4. Répétez les étapes 2 et 3 pour chaque fonction virtuelle, puis pour chaque fonction non virtuelle, jusqu'à ce que l'erreur soit résolue. Si vous êtes toujours bloqué, répétez l'opération pour chaque membre de données statiques.

Exemple
Les détails de la procédure à suivre peuvent varier et parfois se diviser en questions distinctes (comme Qu'est-ce qu'une erreur de référence externe non définie / symbole non résolu et comment puis-je la corriger? ). Je vais cependant fournir un exemple de ce qu'il faut faire dans un cas spécifique qui pourrait dérouter les nouveaux programmeurs.

L'étape 1 mentionne la modification de votre classe afin qu'elle ait une fonction d'un certain type. Si la description de cette fonction vous dépasse, vous pourriez être dans la situation que j'ai l'intention de résoudre. Gardez à l'esprit que c'est une façon d'atteindre l'objectif; ce n'est pas le seul moyen, et il pourrait facilement y avoir de meilleurs moyens dans votre situation spécifique. Appelons votre classe A. Votre destructeur est-il déclaré (dans votre définition de classe) comme

virtual ~A() = default;

ou

virtual ~A() {}

? Si c'est le cas, deux étapes changeront votre destructeur en le type de fonction que nous voulons. Tout d'abord, changez cette ligne en

virtual ~A();

Ensuite, mettez la ligne suivante dans un fichier source qui fait partie de votre projet (de préférence le fichier avec l'implémentation de classe, si vous en avez un):

A::~A() {}

Cela rend votre destructeur (virtuel) non en ligne et non généré par le compilateur. (N'hésitez pas à modifier les choses pour mieux correspondre à votre style de mise en forme de code, par exemple en ajoutant un commentaire d'en-tête à la définition de la fonction.)

JaMiT
la source
Oh, bravo! Pour l'explication très détaillée et très bien cadrée.
David C. Rankin
J'ai dû faire défiler trop loin pour lire ceci. Ayez un vote positif pour l'excellente explication!
Thomas
24

Il y a beaucoup de spéculations dans diverses réponses ici. Je vais donner ci-dessous un code assez minimal qui reproduit cette erreur et expliquer pourquoi elle se produit.

Code assez minimal pour reproduire cette erreur

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

Vous pouvez compiler cela en utilisant GCC comme ceci:

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

Vous pouvez maintenant reproduire l'erreur en supprimant = 0dans IBase.hpp. Je reçois cette erreur:

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

Explication

Notez que le code ci-dessus ne nécessite aucun destructeur, constructeur ou autre fichier supplémentaire pour que la compilation réussisse (bien que vous devriez les avoir).

La façon de comprendre cette erreur est la suivante: Linker recherche le constructeur d'IBase. Il en aura besoin pour le constructeur de Derived. Cependant, étant donné que Derived remplace les méthodes d'IBase, il est associé à vtable qui fera référence à IBase. Lorsque l'éditeur de liens dit "référence non définie à vtable pour IBase", cela signifie essentiellement que Derived a une référence vtable à IBase mais qu'il ne peut trouver aucun code objet compilé d'IBase à rechercher. Donc, l'essentiel est que la classe IBase a des déclarations sans implémentations. Cela signifie qu'une méthode dans IBase est déclarée virtuelle mais nous avons oublié de la marquer comme virtuelle pure OU de fournir sa définition.

Conseil de séparation

Si tout le reste échoue, une façon de déboguer cette erreur est de créer un programme minimal qui compile et de continuer à le modifier pour qu'il atteigne l'état souhaité. Entre les deux, continuez à compiler pour voir quand il commence à échouer.

Remarque sur ROS et Catkin build system

Si vous compiliez l'ensemble de classes ci-dessus dans ROS en utilisant le système de construction catkin, vous aurez besoin des lignes suivantes dans CMakeLists.txt:

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

La première ligne dit essentiellement que nous voulons créer un exécutable nommé myclass et le code pour le construire peut être trouvé dans les fichiers qui suivent. L'un de ces fichiers devrait avoir main (). Notez que vous n'avez pas besoin de spécifier les fichiers .hpp n'importe où dans CMakeLists.txt. Vous n'avez pas non plus à spécifier Derived.cpp comme bibliothèque.

Shital Shah
la source
18

Je viens de rencontrer une autre cause de cette erreur que vous pouvez rechercher.

La classe de base a défini une fonction virtuelle pure comme:

virtual int foo(int x = 0);

Et la sous-classe avait

int foo(int x) override;

Le problème était la faute de frappe qui "=0"devait être en dehors de la parenthèse:

virtual int foo(int x) = 0;

Donc, si vous faites défiler aussi loin, vous n'avez probablement pas trouvé la réponse - c'est autre chose à vérifier.

Philip Thomas
la source
12

Cela peut se produire assez facilement si vous oubliez de créer un lien vers le fichier objet qui a la définition.

Hazok
la source
1
Veuillez ajouter une description supplémentaire à votre réponse et une correction possible.
Mohit Jain
1
Oublier de lier peut inclure oublier d'ajouter des instructions de construction. Dans mon cas, mon fichier cpp avait tout parfaitement `` défini '' sauf que j'avais oublié d'ajouter le fichier cpp à la liste source (dans mon CMakeLists.txt, mais la même chose peut se produire dans d'autres systèmes de construction comme dans un fichier .pro). En conséquence, tout a été compilé et j'ai eu l'erreur au moment du lien ...
sage
@Mohit Jain La façon de lier dans les fichiers objets dépend de la configuration de l'environnement et de l'outillage. Malheureusement, un correctif spécifique pour une personne peut être différent pour une autre (par exemple CMake vs outil propriétaire vs IDE vs etc.)
Hazok
11

Le compilateur GNU C ++ doit décider où mettre le vtablecas où vous avez la définition des fonctions virtuelles d'un objet réparties sur plusieurs unités de compilation (par exemple, certaines des définitions des fonctions virtuelles des objets se trouvent dans un fichier .cpp, d'autres dans un autre. fichier cpp, etc.).

Le compilateur choisit de mettre le vtable au même endroit que celui où la première fonction virtuelle déclarée est définie.

Maintenant, si pour une raison quelconque, vous avez oublié de fournir une définition pour cette première fonction virtuelle déclarée dans l'objet (ou si vous avez oublié par erreur d'ajouter l'objet compilé à la phase de liaison), vous obtiendrez cette erreur.

En tant qu'effet secondaire, veuillez noter que ce n'est que pour cette fonction virtuelle particulière que vous n'obtiendrez pas l'erreur de l'éditeur de liens traditionnel, car il vous manque la fonction foo .

Iulian Popa
la source
8

Pas pour traverser le poteau mais. Si vous avez affaire à un héritage le deuxième succès de Google était ce que j'avais manqué, c'est-à-dire. toutes les méthodes virtuelles doivent être définies.

Tel que:

virtual void fooBar() = 0;

Voir answare C ++ Undefined Reference to vtable and inheritance pour plus de détails. Je viens de réaliser que c'est déjà mentionné ci-dessus, mais diable cela pourrait aider quelqu'un.

Victor Häggqvist
la source
8

Ok, la solution à cela est que vous avez peut-être manqué la définition. Voir l'exemple ci-dessous pour éviter l'erreur du compilateur 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
la source
7
  • Êtes-vous sûr qu'il CDasherComponenta un corps pour le destructeur? Ce n'est certainement pas ici - la question est de savoir s'il se trouve dans le fichier .cc.
  • Du point de vue du style, CDasherModuledevrait définir explicitement son destructeur virtual.
  • Il semble CGameModuleavoir un extra } à la fin (après la}; // for the class ).
  • Est- CGameModulece lié aux bibliothèques qui définissent CDasherModuleet CDasherComponent?
Stephen
la source
- Oui, CDasherComponent a un corps destructeur dans le cpp. Je pensais que cela avait été déclaré dans le .h lorsque j'ai posté cela. - dûment noté. - C'était un support supplémentaire que j'ai ajouté par erreur lors du retrait de la documentation. - Pour autant que je comprends, oui. J'ai modifié un fichier automake que je n'ai pas écrit, mais j'ai suivi les modèles qui ont fonctionné pour d'autres classes avec le même modèle d'héritage des mêmes classes, donc à moins d'avoir fait une erreur stupide (tout à fait possible) , Je ne pense pas que ce soit ça.
RyanG
@RyanG: essayez de déplacer toutes les définitions de fonctions virtuelles dans la définition de classe. Assurez-vous qu'ils sont tous là et voyez si le résultat change.
Stephen
5

Peut-être que manquer le destructeur virtuel est un facteur contributif?

virtual ~CDasherModule(){};
DevByStarlight
la source
5

Ce fut le premier résultat de recherche pour moi, donc j'ai pensé ajouter une autre chose à vérifier: assurez-vous que la définition des fonctions virtuelles est bien dans la classe. Dans mon cas, j'avais ceci:

En tête de fichier:

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

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

et dans mon fichier .cc:

void foo() {
  ...
}

Cela devrait lire

void B::foo() {
}
RedSpikeyThing
la source
3

Pas peut-être. Manque définitivement ~CDasherModule() {}.

Bretzelus
la source
3

Tant de réponses ici, mais aucune ne semblait avoir couvert mon problème. J'avais ce qui suit:


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

Et dans un autre fichier (inclus dans la compilation et la liaison, bien sûr)

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

Eh bien, cela n'a pas fonctionné et j'ai eu l'erreur dont tout le monde parle. Pour le résoudre, j'ai dû déplacer la définition réelle de Foo hors de la déclaration de classe en tant que telle:

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

C::Foo(){
   //bar
}

Je ne suis pas un gourou du C ++ donc je ne peux pas expliquer pourquoi c'est plus correct mais cela a résolu le problème pour moi.

Nathan Daniels
la source
Je ne suis pas un gourou du C ++, mais cela semble être lié au mélange de la déclaration et de la définition dans le même fichier, avec un fichier de définition supplémentaire.
Terry G Lorber
1
Lorsque la définition de fonction était à l'intérieur de votre définition de classe, elle était implicitement déclarée "en ligne". À ce stade, toutes vos fonctions virtuelles étaient intégrées. Lorsque vous avez déplacé la fonction en dehors de la définition de classe, il ne s'agissait plus d'une fonction "en ligne". Puisque vous aviez une fonction virtuelle non intégrée, votre compilateur savait où émettre la vtable. (Une explication plus approfondie ne rentrera pas dans un commentaire.)
JaMiT
3

J'utilisais donc Qt avec Windows XP et le compilateur MinGW et cette chose me rendait fou.

Fondamentalement, le moc_xxx.cpp a été généré vide même lorsque j'ai été ajouté

Q_OBJECT

Supprimer tout ce qui rend les fonctions virtuelles, explicites et tout ce que vous pensez ne fonctionne pas. Enfin, j'ai commencé à supprimer ligne par ligne et il s'est avéré que j'avais

#ifdef something

Autour du fichier. Même lorsque #ifdef était vrai, le fichier moc n'a pas été généré.

La suppression de tous les #ifdefs a donc résolu le problème.

Cette chose ne se produisait pas avec Windows et VS 2013.

Daniel Georgiev
la source
Commentant la ligne Q_OBJECT a rendu ma construction d'application de test simple avec une plaine g++ *.cpp .... (Besoin de quelque chose de rapide et de sale mais qmake était plein de chagrin.)
Nathan Kidd
2

Si tout le reste échoue, recherchez la duplication. J'ai été mal orienté par la référence initiale explicite aux constructeurs et aux destructeurs jusqu'à ce que je lise une référence dans un autre post. C'est n'importe quelle méthode non résolue. Dans mon cas, je pensais avoir remplacé la déclaration qui utilisait char * xml comme paramètre par une utilisant le const inutilement gênant const * xml, mais à la place, j'en avais créé une nouvelle et laissé l'autre en place.

Jerry Miller
la source
2

Il existe de nombreuses possibilités mentionnées pour provoquer cette erreur, et je suis sûr que beaucoup d'entre elles provoquent l'erreur. Dans mon cas, il y avait une autre définition de la même classe, en raison d'une duplication du fichier source. Ce fichier a été compilé, mais pas lié, de sorte que l'éditeur de liens se plaignait de ne pas pouvoir le trouver.

Pour résumer, je dirais que si vous avez regardé la classe assez longtemps et que vous ne voyez pas quel problème de syntaxe possible pourrait en être la cause, recherchez les problèmes de construction comme un fichier manquant ou un fichier dupliqué.

crw4096
la source
2

Dans mon cas, j'utilise Qt et j'avais défini une QObjectsous - classe dans un fichier foo.cpp(pas .h). Le correctif devait être ajouté #include "foo.moc"à la fin de foo.cpp.

Stefan Monov
la source
2

Je pense qu'il vaut également la peine de mentionner que vous obtiendrez également le message lorsque vous essayez de créer un lien vers un objet de n'importe quelle classe qui a au moins une méthode virtuelle et l'éditeur de liens ne peut pas trouver le fichier. Par exemple:

Foo.hpp:

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

Foo.cpp:

#include "Foo.hpp"

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

Compilé avec:

g++ Foo.cpp -c

Et main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Compilé et lié avec:

g++ main.cpp -o main

Donne notre erreur préférée:

/tmp/cclKnW0g.o: Dans la fonction main': main.cpp:(.text+0x1a): undefined reference tovtable pour Foo 'collect2: erreur: ld a renvoyé 1 état de sortie

Cela se produit de mon incompréhensible parce que:

  1. Vtable est créé par classe au moment de la compilation

  2. L'éditeur de liens n'a pas accès à vtable qui se trouve dans Foo.o

Adam Stepniak
la source
1

J'ai eu cette erreur dans le scénario suivant

Prenons un cas où vous avez défini l'implémentation des fonctions membres d'une classe dans le fichier d'en-tête lui-même. Ce fichier d'en-tête est un en-tête exporté (en d'autres termes, il peut être copié dans un fichier commun / inclus directement dans votre base de code). Vous avez maintenant décidé de séparer l'implémentation des fonctions membres dans un fichier .cpp. Après avoir séparé / déplacé l'implémentation vers .cpp, le fichier d'en-tête n'a plus que les prototypes des fonctions membres à l'intérieur de la classe. Après les modifications ci-dessus, si vous créez votre base de code, vous pouvez obtenir l'erreur "référence non définie à 'vtable ...".

Pour résoudre ce problème, avant de créer, assurez-vous de supprimer le fichier d'en-tête (auquel vous avez apporté des modifications) dans le répertoire commun / include. Assurez-vous également que vous modifiez votre makefile pour accueillir / ajouter le nouveau fichier .o qui est construit à partir du nouveau fichier .cpp que vous venez de créer. Lorsque vous effectuez ces étapes, le compilateur / éditeur de liens ne se plaindra plus.

Sidharth Middela
la source
étrange. Si un en-tête doit être copié ailleurs, le système de génération doit mettre à jour la copie automatiquement dès que l'original est modifié, et avant toute inclusion dans un autre fichier. Si vous devez le faire manuellement, vous êtes foutu.
Offirmo
1

J'ai eu ce type d'erreur dans des situations où j'essayais de créer un lien vers un objet lorsque j'ai eu un bug de création qui empêchait l'objet d'être ajouté à l'archive.

Disons que j'ai libXYZ.a qui est censé avoir bioseq.o dans int mais il n'en a pas.

J'ai une erreur:

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

Ceci est différent de tout ce qui précède. J'appellerais cet objet manquant dans le problème d'archivage.

Kemin Zhou
la source
0

Il est également possible que vous obteniez un message comme

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 vous oubliez de définir une fonction virtuelle d'une classe FakeClass1 lorsque vous essayez de lier un test unitaire pour une autre classe SomeClass.

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

Et

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

Dans ce cas, je vous suggère de vérifier à nouveau votre faux pour class1. Vous constaterez probablement que vous avez peut-être oublié de définir une fonction virtuelle ForgottenFuncdans votre fausse classe.

Yuriy
la source
0

J'ai eu cette erreur lorsque j'ai ajouté une deuxième classe à une paire source / en-tête existante. Deux en-têtes de classe dans le même fichier .h et définitions de fonctions pour deux classes dans le même fichier .cpp.

J'ai déjà fait cela avec succès auparavant, avec des classes qui sont censées travailler en étroite collaboration, mais apparemment, quelque chose ne m'a pas plu cette fois. Je ne sais toujours pas quoi, mais les diviser en une classe par unité de compilation l'a corrigé.


La tentative échouée:

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

Encore une fois, l'ajout d'une nouvelle paire source / en-tête et le copier / coller textuellement de la classe IconWithData "ont juste fonctionné".

AaronD
la source
0

Mon cas était stupide, j'ai eu un supplément "après #includepar erreur et devinez quoi?

undefined reference to vtable!

Je me gratte la tête et le visage en palmatisant depuis des heures en commentant les fonctions virtuelles pour voir si quelque chose a changé, et enfin en supprimant le supplément ", tout a été corrigé! Ce genre de choses doit vraiment entraîner une erreur de compilation et non une erreur de liaison.

Par extra ", je veux dire:

#include "SomeHeader.h""
Bahram Pouryousefi
la source
0

Dans mon cas, j'avais une classe de base nommée Person et deux classes dérivées nommées Student et Professor.

Comment mon programme a été corrigé: 1. J'ai créé toutes les fonctions de la classe de base Pure Virtual. 2. J'ai utilisé tous les destructeurs virtuels commedefault ones.

Gaurav Kumar
la source
-3

J'ai eu cette erreur simplement parce que le nom d'un argument constructeur différait dans le fichier d'en-tête et dans le fichier d'implémentation. La signature du constructeur est

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

et ce que j'ai écrit dans la mise en œuvre a commencé par

PointSet (const PointSet & pest, Parent * parent)

ainsi j'ai accidentellement remplacé "pset" par "ravageur". Le compilateur se plaignait de celui-ci et de deux autres constructeurs dans lesquels il n'y avait aucune erreur. J'utilise la version 4.9.1 de g ++ sous Ubuntu. Et la définition d'un destructeur virtuel dans cette classe dérivée n'a fait aucune différence (il est défini dans la classe de base). Je n'aurais jamais trouvé ce bogue si je n'avais pas collé les corps des constructeurs dans le fichier d'en-tête, les définissant ainsi en classe.

vitke
la source
7
Cela ne ferait aucune différence, vous devez avoir eu l'erreur ailleurs et la corriger par inadvertance.
MM