web-dev-qa-db-fra.com

Référence non définie à vtable

Donc, je reçois l'infâme horrible 

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

error pour le code suivant (la classe en question est CGameModule.) et je ne peux pas comprendre le problème à la vie. Au début, je pensais que c'était lié à l'oubli de donner un corps à une fonction virtuelle, mais autant que je sache, 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 devrais fournir.

Remarque: le constructeur est apparemment à l’origine de cette erreur.

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
261
RyanG

Donc, j'ai compris le problème et c'était une combinaison de mauvaise logique et de méconnaissance totale du 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 fichier Make. Donc, je compilais avec un vieux fichier makefile qui ne connaissait absolument pas mes nouveaux fichiers.

Merci pour les réponses et le lien vers la FAQ de GCC. Je veillerai à le lire pour éviter que ce problème ne se produise pour une raison réelle.

48
RyanG

Le GCC FAQ a une entrée:

La solution consiste à 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é pure-virtual [class.dtor]/7.

338
Alexandre Hamez

Pour ce que cela vaut, oublier un corps sur un destructeur virtuel génère les éléments suivants: 

référence non définie à `vtable for CYourClass '. 

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

138
Dan

Si vous utilisez Qt, essayez de réexécuter qmake. Si cette erreur est dans la classe du widget, qmake peut ne pas avoir remarqué que la classe vi de la classe ui devrait être régénérée. Cela a résolu le problème pour moi.

38
gluk47

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: Vous écrivez maintenant une classe C dans laquelle vous allez la tirer de la classe A. 

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

Raison:

functionA est défini en tant que virtuel pur et sa définition est fournie en classe B. .functionB est défini en tant que virtuel (NOT PURE VIRTUAL); sa définition en classe B.

Solution:

  1. Définissez la fonction B comme pure virtuelle (si vous avez une telle exigence) virtual void functionB(parameters) =0; (Cela fonctionne, il est testé)
  2. Fournit la définition de functionB dans la classe A elle-même en la gardant virtuelle . (Espérons que cela fonctionne car je n'ai pas essayé cela)
35

J'ai simplement eu cette erreur parce que mon fichier cpp n'était pas dans le fichier Make.

34
Will

Je viens de rencontrer une autre cause de cette erreur que vous pouvez vérifier.

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 que le "=0" était censé être en dehors de la parenthèse:

virtual int foo(int x) = 0;

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

15
Philip Thomas

Il y a beaucoup de spéculation dans diverses réponses ici. Je donnerai ci-dessous un code assez minimal reproduisant cette erreur et expliquant pourquoi elle se produit. 

Code assez minime 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 ceci en utilisant GCC comme ceci:

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

Vous pouvez maintenant reproduire l'erreur en supprimant = 0 dans 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 virtuel, constructeur ou tout autre fichier supplémentaire pour que la compilation réussisse (bien que vous devriez en avoir). 

Pour comprendre cette erreur, procédez comme suit: Linker recherche le constructeur de IBase. Cela en aura besoin pour le constructeur de Derived. Cependant, comme Derived substitue les méthodes de IBase, il est associé à vtable qui référencera IBase. Lorsque l'éditeur de liens dit "référence non définie à vtable pour IBase", cela signifie essentiellement que Derived possède une référence à vtable vers IBase, mais ne trouve aucun code d'objet compilé d'IBase à rechercher. La ligne de fond est donc que la classe IBase a des déclarations sans implémentations. Cela signifie qu'une méthode dans IBase est déclarée comme virtuelle mais nous avons oublié de la marquer comme pure virtuelle OR en fournissant sa définition.

Pointe de séparation

Si tout échoue, une des façons de déboguer cette erreur consiste à créer un programme minimal compilant, puis à le modifier pour qu'il atteigne l'état souhaité. Entre-temps, continuez à compiler pour voir quand cela commence à échouer.

Note sur le système de construction ROS et Catkin

Si vous compiliez un ensemble de classes dans ROS ci-dessus à l'aide de catkin build system, 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 indique en gros que nous voulons créer un exécutable nommé myclass et que le code permettant de le construire se trouve dans les fichiers suivants. Un de ces fichiers devrait avoir main (). Notez que vous ne devez spécifier aucun fichier .hpp dans CMakeLists.txt. De plus, vous n'avez pas à spécifier Derived.cpp en tant que bibliothèque.

12
Shital Shah

Le compilateur GNU C++ doit décider où placer la variable vtable au cas où vous auriez la définition des fonctions virtuelles d'un objet réparti sur plusieurs unités de compilations (par exemple, certaines des définitions de fonctions virtuelles des objets se trouvent dans un fichier .cpp fichier des autres dans un autre fichier .cpp, etc.). 

Le compilateur choisit de placer la 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 de la première fonction virtuelle déclarée dans l'objet (ou 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 que vous obtiendrez l'erreur de l'éditeur de liens traditionnelle telle que il vous manque la fonction foo .

10
Iulian Popa

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

10
Hazok

Ok, la solution à cela est que vous avez peut-être manqué la définition. Voir l'exemple ci-dessous pour éviter l'erreur de compilation 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
Sammy

Pas pour traverser mais. Si vous avez affaire à inheritance, le deuxième hit de Google a été ce que j'avais oublié toutes les méthodes virtuelles doivent être définies.

Tel que:

virtual void fooBar() = 0;

Voir answare Référence non définie C++ à vtable et héritage pour plus de détails. Je viens de me rendre compte que c'est déjà mentionné ci-dessus, mais bon, ça pourrait aider quelqu'un.

7
Victor Häggqvist
  • Êtes-vous sûr que CDasherComponent a un corps pour le destructeur? Ce n'est certainement pas là - la question est de savoir si c'est dans le fichier .cc.
  • Dans une perspective de style, CDasherModule devrait définir explicitement son destructeur virtual.
  • Il semble que CGameModule ait un } supplémentaire à la fin (après le }; // for the class).
  • CGameModule est-il lié aux bibliothèques qui définissent CDasherModule et CDasherComponent?
6
Stephen

Peut-être que l'absence du destructeur virtuel est un facteur contributif?

virtual ~CDasherModule(){};
5
DevByStarlight

C'était le premier résultat de recherche pour moi, alors j'ai pensé ajouter une autre chose à vérifier: assurez-vous que la définition des fonctions virtuelles est bien définie 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() {
}
5
RedSpikeyThing

Tant de réponses ici mais aucune d’entre elles ne semblait avoir couvert mon problème. J'ai eu le suivant:


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

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

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

Cela n'a pas fonctionné et j'ai eu l'erreur dont tout le monde parle. Pour résoudre ce problème, j'ai dû déplacer la définition même de Foo 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.

3
Nathan Daniels

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

Fondamentalement, le fichier 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 supposez ne fonctionne pas. Finalement, 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’était pas généré.

Donc, supprimer tous les #ifdefs a résolu le problème.

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

3
Daniel Georgiev

Pas peut-être. ~CDasherModule() {} est manquant. 

3
Bretzelus

Dans mon cas, j'utilise Qt et j'avais défini une sous-classe QObject dans un fichier foo.cpp (et non .h). Le correctif consistait à ajouter #include "foo.moc" à la fin de foo.cpp.

2
Stefan Monov

Si tout échoue, recherchez la duplication. J'ai été mal dirigé par la référence initiale explicite aux constructeurs et 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 en tant que paramètre avec une déclaration utilisant inutilement gênante const char * xml, mais au lieu de cela, j'avais créé une nouvelle déclaration et laissé l'autre en place.

2
Jerry Miller

Il y a beaucoup de possibilités mentionnées pour causer cette erreur, et je suis sûr que beaucoup en sont la cause. Dans mon cas, il y avait une autre définition de la même classe, due à 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 regardez la classe assez longtemps et que vous ne pouvez pas voir quel problème de syntaxe pourrait être la cause, recherchez les problèmes de construction tels qu'un fichier manquant ou un fichier dupliqué.

2
crw4096

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 bogue de création qui empêchait l'ajout de l'objet à l'archive.

Disons que j'ai libXYZ.a qui est supposé avoir bioseq.o dans int mais ce n’est pas le cas. 

J'ai une erreur:

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

C'est quit différent de tout ce qui précède. J'appellerais cet objet manquant dans le problème de l'archive.

1
Kemin Zhou

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

Prenons le cas où vous avez défini l'implémentation des fonctions membres d'une classe dans le fichier d'en-tête 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 common/include directement dans votre base de code). Vous avez maintenant décidé de séparer la mise en oeuvre 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 ne contient plus que les prototypes des fonctions membres de la classe. Après les modifications ci-dessus, si vous construisez votre base de code, vous obtiendrez l'erreur "référence non définie à 'vtable ...".

Pour résoudre ce problème, avant de construire, assurez-vous de supprimer le fichier d’en-tête (dans lequel vous avez apporté des modifications) dans le répertoire common/include. Assurez-vous également que vous modifiez votre fichier Make pour accueillir/ajouter le nouveau fichier .o créé à partir du nouveau fichier .cpp que vous venez de créer. Lorsque vous suivez ces étapes, le compilateur/éditeur de liens ne se plaint plus.

1
Sidharth Middela

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 fonction pour deux classes dans le même fichier .cpp.

Je l'avais déjà fait avec succès auparavant, avec des cours destinés à travailler en étroite collaboration, mais apparemment, quelque chose ne m'aimait pas cette fois-ci. Je ne sais toujours pas quoi, mais les diviser en une classe par unité de compilation a réglé le problème.


La tentative raté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, ajouter une nouvelle paire source/en-tête et couper/coller la classe IconWithData in extenso dans "juste travaillé".

0
AaronD

Qu'est-ce qu'un vtable?

Il peut être utile de savoir de quoi parle le message d'erreur avant d'essayer de le réparer. Je vais commencer à un niveau élevé, puis quelques détails supplémentaires. De cette façon, les gens peuvent sauter une fois qu'ils sont à l'aise avec leur compréhension de vtables. … Et il y a beaucoup de gens qui sautent en avant maintenant. :) Pour ceux qui restent:

Une vtable est fondamentalement l'implémentation la plus courante de polymorphismen C++ . Lorsque vtables est utilisé, chaque classe polymorphe a une vtable quelque part dans le programme; vous pouvez le considérer comme un membre (caché) static de la classe. Chaque objet d'une classe polymorphe est associé à la table vtable pour sa classe la plus dérivée. En vérifiant cette association, le programme peut exploiter sa magie polymorphe. Avertissement important: une table virtuelle est un détail d'implémentation. Cela n’est pas imposé par le standard C++, même si la plupart des compilateurs C++ (tous?) 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 à en dévier!

Chaque objet polymorphe a un pointeur (caché) sur la table vtable 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 indiquer le type "réel" d'un objet (sauf pendant la construction, mais ignorons ce cas particulier). Par exemple, si un objet de type A ne pointe pas sur la table vtable de A, cet objet est en réalité un sous-objet de quelque chose dérivé de A.

Le nom "vtable" vient de " v fonction virtuelle table ". C'est une table qui stocke des pointeurs vers des fonctions (virtuelles). Un compilateur choisit sa convention pour la disposition 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 sur une table, accède à l'entrée associée à la fonction souhaitée, puis utilise le pointeur de fonction stocké pour appeler la fonction correcte. Il existe différentes astuces pour réussir ce travail, mais je n’entrerai pas dans celles-ci.

Où/quand une vtable est-elle générée?

Une vtable est automatiquement générée (parfois appelée "émise") par le compilateur. Un compilateur peut émettre une vtable dans chaque unité de traduction qui voit une définition de classe polymorphe, mais ce serait généralement une surcharge inutile. Une alternative ( tilisée par gcc , et probablement par d’autres) consiste à choisir une seule unité de traduction dans laquelle placer la table vtable, comme vous le feriez pour un seul fichier source dans lequel placer une classe ' membres de données statiques. Si ce processus de sélection ne parvient pas à sélectionner les unités de traduction, la vtable devient une référence indé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, la vtable devient une référence indé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épondants qui ont mentionné cette possibilité. Je l'aurais probablement oublié autrement.)

Le processus de sélection utilisé par gcc est logique si nous commençons par la tradition consistant à consacrer un fichier source (unique) à chaque classe qui en a besoin d’un pour sa mise en œuvre. Ce serait bien d’émettre le 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 suivie. Ainsi, au lieu de rechercher l'implémentation de la classe entière, examinons l'implémentation d'un membre spécifique de la classe. Si la tradition est suivie - et si ce membre est effectivement implémenté -, l'objectif est alors atteint.

Le membre sélectionné par gcc (et potentiellement par d'autres compilateurs) est la première fonction virtuelle non-inline qui n'est pas virtuelle. Si vous faites partie de la foule qui déclare les constructeurs et les destructeurs avant les autres fonctions membres, ce destructeur a de bonnes chances d'être sélectionné. (Vous vous êtes souvenu de rendre le destructeur virtuel, non?) Il y a des exceptions; Je suppose que les exceptions les plus courantes sont 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 peut 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 corrigé cette situation, mais je ne connais pas les numéros de version pertinents. Je pourrais essayer de regarder cela, mais il est plus facile de coder ou d’attendre que le compilateur se plaint.

En résumé, il y a trois causes principales de l'erreur "référence indéfinie à vtable":

  1. Une fonction membre n'a pas 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. Ce sont plutôt ce que 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. Ne vous attendez pas à ce que la résolution de ces situations résolve cette erreur.

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

Comment réparer l'erreur?

Accueillez les gens qui sautent! :)

  1. Regardez la définition de votre classe. Recherchez la première fonction virtuelle non intégrée qui n'est pas virtuelle (et non "= 0") et dont vous fournissez la définition (et non "= default").
    • Si cette fonction n'existe pas, essayez de modifier votre classe pour en créer une. (Erreur éventuellement résolue.)
    • Voir aussi 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 statique.

Exemple
Les détails de ce que vous pouvez faire peuvent varier, et parfois se diviser en plusieurs questions distinctes (comme Qu'est-ce qu'une erreur de symbole externe non définie/référence non résolue et comment puis-je la résoudre? ). Je vais cependant donner un exemple de ce qu'il faut faire dans un cas spécifique qui pourrait embrouiller 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 échappe, vous vous trouvez peut-être dans la situation que je compte aborder. N'oubliez pas qu'il s'agit d'un moyen 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 étant

virtual ~A() = default;

ou

virtual ~A() {}

? Si c'est le cas, deux étapes vont changer votre destructeur dans le type de fonction que nous voulons. Commencez par changer cette ligne en

virtual ~A();

Deuxièmement, placez la ligne suivante dans un fichier source faisant partie de votre projet (préférablement le fichier avec l'implémentation de classe, si vous en avez un):

A::~A() {}

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

0
JaMiT

Il est également possible que vous receviez 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 votre faux pour class1 une fois de plus. Vous constaterez probablement que vous avez peut-être oublié de définir une fonction virtuelle ForgottenFunc dans votre classe de faux.

0
Yuriy

Je pense qu'il convient également de mentionner que vous recevrez é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 sortie statut

Ceci se produit de mon incompréhension parce que:

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

  2. Linker n'a pas accès à vtable qui est dans Foo.o

0
Adam Stepniak