it-swarm.com.de

Nicht definierter Verweis auf vtable

Also werde ich das unendlich grässliche 

undefinierter Verweis auf 'vtable ...

fehler für den folgenden Code (die betreffende Klasse ist CGameModule.) und ich kann nicht verstehen, was das Problem ist. Zuerst dachte ich, dass es damit zusammenhängt, zu vergessen, einer virtuellen Funktion einen Körper zu geben, aber soweit ich es verstehe, ist alles hier. Die Vererbungskette ist etwas lang, aber hier ist der zugehörige Quellcode. Ich bin nicht sicher, welche anderen Informationen ich liefern sollte.

Anmerkung: Der Konstruktor ist der Ort, an dem dieser Fehler auftritt, wie es scheint.

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

Erbt von ...

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

Was erbt von ....

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

Also, ich habe das Problem herausgefunden und es war eine Kombination aus schlechter Logik und nicht völlig vertraut mit der Welt der Automake/Autotools. Ich habe die richtigen Dateien zu meiner Makefile.am-Vorlage hinzugefügt, aber ich war mir nicht sicher, in welchem ​​Schritt unseres Build-Prozesses das Makefile tatsächlich erstellt wurde. Also kompilierte ich mit einem alten Makefile, das keinerlei Ahnung von meinen neuen Dateien hatte.

Vielen Dank für die Antworten und den Link zu den GCC-FAQ. Ich werde das sicher lesen, um zu vermeiden, dass dieses Problem aus einem echten Grund auftritt.

48
RyanG

Das GCC FAQ hat einen Eintrag:

Die Lösung besteht darin, sicherzustellen, dass alle virtuellen Methoden definiert werden, die nicht rein sind. Beachten Sie, dass ein Destruktor definiert werden muss, auch wenn er als rein virtuell deklariert ist.

338
Alexandre Hamez

Wenn man einen Körper auf einem virtuellen Destruktor vergisst, erzeugt dies Folgendes: 

undefinierter Verweis auf `vtable for CYourClass '. 

Ich füge eine Notiz hinzu, weil die Fehlermeldung täuschend ist. (Dies war mit gcc Version 4.6.3.)

138
Dan

Wenn Sie Qt verwenden, versuchen Sie, qmake erneut auszuführen. Wenn sich dieser Fehler in der Widgetklasse befindet, hat qmake möglicherweise nicht bemerkt, dass die vi-Tabelle der Ui-Klasse neu generiert werden sollte. Dies hat das Problem für mich behoben.

38
gluk47

Ein unbestimmter Verweis auf vtable kann auch aufgrund der folgenden Situation auftreten. Probieren Sie einfach folgendes aus:

Klasse A enthält:

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

Klasse B enthält:

  1. Die Definition für die obige FunktionA. 
  2. Die Definition für die obige Funktion.

Klasse C enthält: Jetzt schreiben Sie eine Klasse C, in der Sie sie von Klasse A ableiten werden. 

Wenn Sie nun versuchen zu kompilieren, erhalten Sie eine undefinierte Referenz auf vtable für Klasse C als Fehler.

Grund:

functionA ist als rein virtuell definiert, und ihre Definition ist in Klasse B enthaltenfunctionB ist als virtuell definiert (NOT PURE VIRTUAL). Daher versucht sie, ihre Definition in Klasse A selbst zu finden.

Lösung:

  1. Machen Sie Funktion B als rein virtuell (wenn Sie eine solche Anforderung haben) virtual void functionB(parameters) =0; (Dies funktioniert, wird getestet)
  2. Definiere Definition für functionB in Klasse A selbst und halte es als virtuell .. (Hoffe, es funktioniert, da ich dies nicht versucht habe)
35

Ich habe einfach diesen Fehler erhalten, weil meine CPP-Datei nicht im Makefile war.

34
Will

Ich bin gerade auf eine andere Ursache für diesen Fehler gestoßen, den Sie überprüfen können.

Die Basisklasse definierte eine reine virtuelle Funktion als:

virtual int foo(int x = 0);

Und die Unterklasse hatte

int foo(int x) override;

Das Problem war der Tippfehler, dass der "=0" außerhalb der Klammer stehen sollte:

virtual int foo(int x) = 0;

Falls Sie also so weit nach unten scrollen, haben Sie wahrscheinlich keine Antwort gefunden.

15
Philip Thomas

Hier wird in verschiedenen Antworten viel spekuliert. Ich werde im Folgenden einen ziemlich minimalen Code angeben, der diesen Fehler reproduziert, und erklären, warum er auftritt. 

Ziemlich minimaler Code zum Reproduzieren dieses Fehlers

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

Sie können dies mit GCC folgendermaßen kompilieren:

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

Sie können den Fehler jetzt reproduzieren, indem Sie = 0 in IBase.hpp entfernen. Ich erhalte diesen Fehler:

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

Erklärung

Beachten Sie, dass der obige Code keine virtuellen Destruktoren, Konstruktoren oder andere zusätzliche Dateien für das Kompilieren erfordert, um erfolgreich zu sein (obwohl Sie sie haben sollten). 

Um diesen Fehler zu verstehen, gehen Sie folgendermaßen vor: Der Linker sucht nach dem Konstruktor von IBase. Dies wird es für den Konstruktor von Derived benötigen. Da Abgeleitete Methoden von IBase überschrieben werden, ist jedoch eine eigene Tabelle vorhanden, die auf IBase verweist. Wenn der Linker "undefined Verweis auf vtable für IBase" sagt, bedeutet dies im Wesentlichen, dass Derived einen vtable-Verweis auf IBase hat, aber keinen kompilierten Objektcode von IBase finden kann, zu dem er nachschlagen kann. Im Endeffekt enthält die Klasse IBase Deklarationen ohne Implementierungen. Dies bedeutet, dass eine Methode in IBase als virtuell deklariert ist. Wir haben jedoch vergessen, sie als rein virtuell zu kennzeichnen OR und ihre Definition angeben.

Abstechspitze

Wenn alles andere fehlschlägt, besteht eine Möglichkeit zum Debuggen dieses Fehlers darin, ein minimales Programm zu erstellen, das kompiliert wird, und es weiterhin zu ändern, damit es in den gewünschten Zustand gelangt. Dazwischen weiter kompilieren, um zu sehen, wann der Vorgang fehlschlägt.

Hinweis zum ROS- und Catkin-Buildsystem

Wenn Sie in ROS mit catkin build system übergeordnete Klassen kompiliert haben, benötigen Sie folgende Zeilen in CMakeLists.txt:

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

Die erste Zeile besagt im Wesentlichen, dass wir eine ausführbare Datei namens myclass erstellen möchten, und der Code zum Erstellen dieser Dateien kann in folgenden Dateien gefunden werden. Eine dieser Dateien sollte main () haben. Beachten Sie, dass Sie in CMakeLists.txt keine .hpp-Dateien angeben müssen. Sie müssen Derived.cpp auch nicht als Bibliothek angeben.

12
Shital Shah

Der C++ - Compiler GNU muss entscheiden, wo er vtable ablegt, falls Sie die Definition der virtuellen Funktionen eines Objekts auf mehrere Compilation-Units verteilt haben (z. B. enthalten einige der Definitionen der virtuellen Funktionen von Objekten eine .cpp Datei andere in einer anderen .cpp-Datei und so weiter). 

Der Compiler legt fest, dass vtable an derselben Stelle abgelegt wird, an der die erste deklarierte virtuelle Funktion definiert ist.

Wenn Sie aus irgendeinem Grund vergessen haben, eine Definition für die erste im Objekt deklarierte virtuelle Funktion anzugeben (oder versehentlich vergessen, das kompilierte Objekt in der Verknüpfungsphase hinzuzufügen), wird dieser Fehler angezeigt. 

Als Nebeneffekt beachten Sie bitte, dass Sie nur bei dieser virtuellen Funktion nicht den herkömmlichen Linker-Fehler wie erhalten, da Sie die Funktion foo nicht finden.

10
Iulian Popa

Dies kann sehr leicht passieren, wenn Sie vergessen, eine Verknüpfung zu der Objektdatei herzustellen, die die Definition enthält.

10
Hazok

Ok, die Lösung dafür ist, dass Sie die Definition möglicherweise verpasst haben. Siehe das Beispiel unten, um den vtable-Compiler-Fehler zu vermeiden:

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

Nicht zu überqueren, aber. Wenn Sie es mit Vererbung zu tun haben, war der zweite Google-Treffer das, was ich übersehen hatte, dh. Alle virtuellen Methoden sollten definiert werden.

Sowie:

virtual void fooBar() = 0;

Siehe answer C++ undefined Verweis auf vtable und vererbung für Details. Ich habe gerade erkannt, dass es oben bereits erwähnt wurde, aber es könnte jemandem helfen.

7
  • Sind Sie sicher, dass CDasherComponent einen Körper für den Destruktor hat? Es ist definitiv nicht hier - die Frage ist, ob es in der .cc-Datei ist.
  • Aus Sicht des Stils sollte CDasherModule den Destruktor virtual explizit definieren.
  • Es sieht so aus, als hätte CGameModule am Ende (nach dem }) einen zusätzlichen }; // for the class.
  • Wird CGameModule mit den Bibliotheken verknüpft, die CDasherModule und CDasherComponent definieren?
6
Stephen

Vielleicht ist das Fehlen des virtuellen Destruktors ein Faktor?

virtual ~CDasherModule(){};
5
DevByStarlight

Dies war das erste Suchergebnis für mich, also dachte ich, ich würde noch etwas hinzufügen, um zu überprüfen: Vergewissern Sie sich, dass die Definition der virtuellen Funktionen tatsächlich in der Klasse enthalten ist. In meinem Fall hatte ich folgendes:

Header-Datei:

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

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

und in meiner .cc-Datei:

void foo() {
  ...
}

Das sollte lesen

void B::foo() {
}
5
RedSpikeyThing

So viele Antworten hier, aber keine davon schien mein Problem aufgedeckt zu haben. Ich hatte folgendes:


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

Und in einer anderen Datei (natürlich in der Zusammenstellung und Verknüpfung)

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

Nun, das hat nicht funktioniert und ich habe den Fehler bekommen, über den alle sprechen. Um das Problem zu lösen, musste ich die eigentliche Definition von Foo aus der Klassendeklaration als solche verschieben:

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

C::Foo(){
   //bar
}

Ich bin kein C++ - Guru, daher kann ich nicht erklären, warum dies korrekter ist, aber das Problem wurde für mich gelöst.

3
Nathan Daniels

Also habe ich Qt mit Windows XP und MinGW-Compiler verwendet und dieses Ding hat mich verrückt gemacht.

Grundsätzlich wurde die moc_xxx.cpp leer generiert, auch wenn ich hinzugefügt wurde

Q_OBJECT

Wenn Sie alles löschen, was Funktionen virtuell, explizit macht und was immer Sie vermuten, funktioniert dies nicht. Schließlich entfernte ich Zeile für Zeile und es stellte sich heraus, dass ich es hatte

#ifdef something

Um die Datei. Selbst wenn die Datei #ifdef true war, wurde keine Moc-Datei erstellt.

Das Entfernen aller #ifdefs hat das Problem behoben.

Dieses Ding passierte nicht mit Windows und VS 2013.

3
Daniel Georgiev

Vielleicht nicht Auf jeden Fall fehlt ~CDasherModule() {}

3
Bretzelus

In meinem Fall verwende ich Qt und hatte eine QObject-Unterklasse in einer foo.cpp-Datei (nicht .h) definiert. Das Update bestand darin, am Ende von #include "foo.moc"foo.cpp hinzuzufügen.

2
Stefan Monov

Wenn alles andere fehlschlägt, suchen Sie nach Duplikaten. Ich wurde durch den expliziten anfänglichen Verweis auf Konstruktoren und Destruktoren fehlgeleitet, bis ich einen Verweis in einem anderen Beitrag gelesen habe. Es ist jede unaufgelöste Methode. In meinem Fall dachte ich, ich hätte die Deklaration, die char * xml als Parameter verwendete, durch eine mit dem unnötig störenden const char * xml ersetzt, stattdessen hatte ich ein neues erstellt und das andere an Ort und Stelle gelassen.

2
Jerry Miller

Es gibt viele Möglichkeiten, um diesen Fehler zu verursachen, und ich bin sicher, dass viele von ihnen den Fehler verursachen. In meinem Fall gab es eine andere - Definition derselben Klasse aufgrund einer Verdoppelung der Quelldatei. Diese Datei wurde kompiliert, aber nicht verlinkt, daher beschwerte sich der Linker, dass er sie nicht finden konnte.

Zusammenfassend würde ich sagen, wenn Sie die Klasse lange genug angestarrt haben und nicht herausfinden können, welches mögliche Syntaxproblem dies verursachen könnte, suchen Sie nach Build-Problemen wie einer fehlenden oder einer duplizierten Datei.

2
crw4096

Ich denke, es ist auch erwähnenswert, dass Sie auch die Nachricht erhalten, wenn Sie versuchen, einen Link zu einem Objekt von jede Klasse zu erstellen, das mindestens eine virtuelle Methode und einen Linker hat kann nicht finden die Datei. Zum Beispiel:

Foo.hpp:

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

Foo.cpp:

#include "Foo.hpp"

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

Zusammengestellt mit:

g++ Foo.cpp -c

Und main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Zusammengestellt und verlinkt mit:

g++ main.cpp -o main

Gibt unseren Lieblingsfehler:

/tmp/cclKnW0g.o: In Funktion main': main.cpp:(.text+0x1a): undefined reference to vtable für Foo 'collect2: Fehler: ld hat 1 Exit-Status zurückgegeben

Dies ist auf mein Verständnis zurückzuführen, weil:

  1. Vtable wird zur Kompilierungszeit pro Klasse erstellt

  2. Der Linker hat keinen Zugriff auf die vtable in Foo.o

2
Adam Stepniak

Diese Art von Fehler ist in Situationen aufgetreten, in denen ich versuchte, eine Verknüpfung zu einem Objekt herzustellen, als ich einen Fehler beim Erstellen eines Bugs hatte, der das Hinzufügen des Objekts zum Archiv verhinderte.

Angenommen, ich habe libXYZ.a, das bioseq.o in int haben soll, aber nicht. 

Ich habe einen Fehler erhalten:

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

Dies unterscheidet sich von allen oben genannten. Ich würde dieses fehlende Objekt im Archivproblem nennen.

1
Kemin Zhou

Ich habe diesen Fehler im folgenden Szenario erhalten

Stellen Sie sich einen Fall vor, in dem Sie die Implementierung von Memberfunktionen einer Klasse in der Headerdatei selbst definiert haben. Diese Header-Datei ist eine exportierte Header-Datei (dh sie kann in ein Common/Include direkt in Ihre Codebase kopiert werden). Jetzt haben Sie beschlossen, die Implementierung der Member-Funktionen in die .cpp-Datei zu trennen. Nachdem Sie die Implementierung in .cpp getrennt/verschoben haben, enthält die Header-Datei nur noch die Prototypen der Member-Funktionen in der Klasse. Wenn Sie nach den obigen Änderungen Ihre Codebase erstellen, wird möglicherweise die Fehlermeldung "undefined Verweis auf 'vtable ..." angezeigt.

Um dies zu beheben, müssen Sie vor dem Erstellen die Header-Datei (an der Sie Änderungen vorgenommen haben) im common/include-Verzeichnis löschen. Stellen Sie außerdem sicher, dass Sie Ihr Makefile ändern, um die neue .o-Datei aufzunehmen/hinzuzufügen, die aus der gerade erstellten neuen .cpp-Datei erstellt wird. Wenn Sie diese Schritte ausführen, beschwert sich der Compiler/Linker nicht mehr.

1

Ich habe diese Fehlermeldung erhalten, als ich einem vorhandenen Quell-/Header-Paar eine zweite Klasse hinzugefügt habe. Zwei Klassenheader in derselben .h-Datei und Funktionsdefinitionen für zwei Klassen in derselben CPP-Datei.

Ich habe das schon früher erfolgreich gemacht, mit Klassen, die eng zusammenarbeiten sollen, aber anscheinend hat mich diesmal etwas nicht gefallen. Ich weiß immer noch nicht was, aber die Unterteilung in eine Klasse pro Compilationseinheit hat das Problem behoben.


Der fehlgeschlagene Versuch:

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

Wieder fügen Sie ein neues Quell-/Header-Paar hinzu und schneiden/fügen Sie die IconWithData-Klasse wortwörtlich dort ein, "hat gerade gearbeitet".

0
AaronD

Was ist ein vtable?

Es kann hilfreich sein, zu wissen, worüber die Fehlermeldung spricht, bevor Sie versuchen, sie zu beheben. Ich werde auf einem hohen Niveau beginnen und mich dann mit einigen Details befassen. Auf diese Weise können die Benutzer weitermachen, sobald sie mit dem Verständnis von vtables vertraut sind. … Und da hüpfen gerade ein paar Leute voran. :) Für diejenigen, die hier bleiben:

Eine vtable ist im Grunde die häufigste Implementierung von Polymorphismusin C++ . Wenn vtables verwendet werden, hat jede polymorphe Klasse irgendwo im Programm eine vtable. Sie können es sich als (verstecktes) static Datenelement der Klasse vorstellen. Jedes Objekt einer polymorphen Klasse ist der vtable für die am häufigsten abgeleitete Klasse zugeordnet. Durch Überprüfen dieser Zuordnung kann das Programm seine polymorphe Magie bearbeiten. Wichtiger Hinweis: Eine vtable ist ein Implementierungsdetail. Der C++ - Standard schreibt dies nicht vor, obwohl die meisten (alle?) C++ - Compiler vtables verwenden, um polymorphes Verhalten zu implementieren. Die Details, die ich präsentiere, sind entweder typische oder vernünftige Ansätze. Compiler dürfen hiervon abweichen!

Jedes polymorphe Objekt hat einen (versteckten) Zeiger auf die vtable für die am häufigsten abgeleitete Klasse des Objekts (möglicherweise mehrere Zeiger in den komplexeren Fällen). Durch Betrachten des Zeigers kann das Programm erkennen, was der "echte" Typ eines Objekts ist (außer während der Erstellung, aber lassen Sie uns diesen Sonderfall überspringen). Wenn beispielsweise ein Objekt vom Typ A nicht auf die Tabelle von A verweist, ist dieses Objekt tatsächlich ein Unterobjekt von etwas, das von A abgeleitet ist.

Der Name "vtable" stammt von " v irtual function table ". In dieser Tabelle werden Zeiger auf (virtuelle) Funktionen gespeichert. Ein Compiler wählt seine Konvention für das Layout der Tabelle. Ein einfacher Ansatz besteht darin, die virtuellen Funktionen in der Reihenfolge durchzugehen, in der sie in den Klassendefinitionen deklariert sind. Wenn eine virtuelle Funktion aufgerufen wird, folgt das Programm dem Zeiger des Objekts auf eine vtable, wechselt zu dem Eintrag, der der gewünschten Funktion zugeordnet ist, und ruft dann mit dem gespeicherten Funktionszeiger die richtige Funktion auf. Es gibt verschiedene Tricks, um diese Arbeit zu machen, aber ich werde hier nicht darauf eingehen.

Wo/wann wird ein vtable generiert?

Eine vtable wird vom Compiler automatisch generiert (manchmal als "ausgegeben" bezeichnet). Ein Compiler könnte eine vtable in jeder Übersetzungseinheit ausgeben, die eine polymorphe Klassendefinition sieht, aber das wäre normalerweise unnötiger Overkill. Eine Alternative ( von gcc verwendet und wahrscheinlich auch von anderen) besteht darin, eine einzelne Übersetzungseinheit auszuwählen, in der die vtable platziert werden soll, ähnlich wie Sie eine einzelne Quelldatei auswählen würden, in die eine Klasse eingefügt werden soll. ' statische Datenelemente. Wenn dieser Auswahlprozess keine Übersetzungseinheiten auswählt, wird die vtable zu einer undefinierten Referenz. Daher der Fehler, dessen Botschaft freilich nicht besonders deutlich ist.

In ähnlicher Weise wird die vtable zu einer undefinierten Referenz, wenn der Auswahlprozess eine Übersetzungseinheit auswählt, diese Objektdatei jedoch nicht für den Linker bereitgestellt wird. Leider kann die Fehlermeldung in diesem Fall noch weniger eindeutig sein als in dem Fall, in dem der Auswahlprozess fehlgeschlagen ist. (Vielen Dank an die Antwortenden, die diese Möglichkeit erwähnt haben. Ich hätte sie wahrscheinlich sonst vergessen.)

Der von gcc verwendete Auswahlprozess ist sinnvoll, wenn wir mit der Tradition beginnen, jeder Klasse, die eine für ihre Implementierung benötigt, eine (einzelne) Quelldatei zuzuweisen. Es wäre schön, die vtable beim Kompilieren dieser Quelldatei auszugeben. Nennen wir das unser Ziel. Das Auswahlverfahren muss jedoch funktionieren, auch wenn diese Tradition nicht befolgt wird. Suchen wir also nicht nach der Implementierung der gesamten Klasse, sondern nach der Implementierung eines bestimmten Mitglieds der Klasse. Wenn der Tradition gefolgt wird - und , wenn dieses Mitglied tatsächlich implementiert ist - dann ist das Ziel erreicht.

Das von gcc (und möglicherweise von anderen Compilern) ausgewählte Element ist die erste virtuelle Nicht-Inline-Funktion, die nicht rein virtuell ist. Wenn Sie Teil der Menge sind, die Konstruktoren und Destruktoren vor anderen Mitgliedsfunktionen deklariert, hat dieser Destruktor gute Chancen, ausgewählt zu werden. (Sie haben daran gedacht, den Destruktor virtuell zu machen, oder?) Es gibt Ausnahmen; Ich würde erwarten, dass die häufigsten Ausnahmen sind, wenn eine Inline-Definition für den Destruktor bereitgestellt wird und wenn der Standard-Destruktor angefordert wird (mit "= default").

Der Kluge kann feststellen, dass eine polymorphe Klasse Inline-Definitionen für alle ihre virtuellen Funktionen bereitstellen darf. Schlägt der Auswahlprozess dadurch nicht fehl? Dies ist bei älteren Compilern der Fall. Ich habe gelesen, dass die neuesten Compiler diese Situation behoben haben, kenne aber keine relevanten Versionsnummern. Ich könnte versuchen, dies nachzuschlagen, aber es ist einfacher, entweder Code darum herum zu schreiben oder darauf zu warten, dass sich der Compiler beschwert.

Zusammenfassend gibt es drei Hauptursachen für den Fehler "undefinierter Verweis auf vtable":

  1. Einer Mitgliedsfunktion fehlt ihre Definition.
  2. Eine Objektdatei wird nicht verknüpft.
  3. Alle virtuellen Funktionen haben Inline-Definitionen.

Diese Ursachen allein reichen nicht aus, um den Fehler selbst zu verursachen. Vielmehr wenden Sie sich an diese Adresse, um den Fehler zu beheben. Erwarten Sie nicht, dass das absichtliche Erstellen einer dieser Situationen definitiv zu diesem Fehler führt. es gibt andere anforderungen. Erwarten Sie, dass durch Beheben dieser Situationen dieser Fehler behoben wird.

(OK, Nummer 3 hätte möglicherweise ausgereicht, als diese Frage gestellt wurde.)

Wie behebe ich den Fehler?

Willkommen zurück, Leute, die weiterspringen! :)

  1. Schauen Sie sich Ihre Klassendefinition an. Suchen Sie die erste virtuelle Nicht-Inline-Funktion, die nicht rein virtuell ist (nicht "= 0") und deren Definition Sie angeben (nicht "= default").
    • Wenn es keine solche Funktion gibt, versuchen Sie, Ihre Klasse so zu ändern, dass es eine gibt. (Fehler möglicherweise behoben.)
    • Siehe auch die Antwort von Philip Thomas für eine Einschränkung.
  2. Suchen Sie die Definition für diese Funktion. Wenn es fehlt, füge es hinzu! (Fehler möglicherweise behoben.)
  3. Überprüfen Sie Ihren Link-Befehl. Wenn die Objektdatei mit der Definition dieser Funktion nicht erwähnt wird, beheben Sie dies! (Fehler möglicherweise behoben.)
  4. Wiederholen Sie die Schritte 2 und 3 für jede virtuelle Funktion und dann für jede nicht virtuelle Funktion, bis der Fehler behoben ist. Wenn Sie immer noch nicht weiterkommen, wiederholen Sie dies für jedes statische Datenelement.

Beispiel
Die Details, was zu tun ist, können variieren und verzweigen sich manchmal in separate Fragen (wie Was ist ein undefinierter Verweis/ungelöster externer Symbolfehler und wie behebe ich ihn? ). Ich werde jedoch ein Beispiel dafür geben, was in einem bestimmten Fall zu tun ist, der neuere Programmierer verwirren könnte.

In Schritt 1 wird erwähnt, wie Sie Ihre Klasse so ändern, dass sie eine Funktion eines bestimmten Typs hat. Wenn die Beschreibung dieser Funktion über Ihren Kopf ging, könnten Sie sich in der Situation befinden, auf die ich zu sprechen beabsichtige. Denken Sie daran, dass dies ein Weg ist, um das Ziel zu erreichen. Es ist nicht der einzige Weg, und es könnte leicht bessere Wege in Ihrer spezifischen Situation geben. Nennen wir Ihre Klasse A. Ist Ihr Destruktor (in Ihrer Klassendefinition) als entweder deklariert

virtual ~A() = default;

oder

virtual ~A() {}

? In diesem Fall wird Ihr Destruktor in zwei Schritten in die gewünschte Funktion umgewandelt. Ändern Sie zuerst diese Zeile in

virtual ~A();

Fügen Sie zweitens die folgende Zeile in eine Quelldatei ein, die Teil Ihres Projekts ist (vorzugsweise die Datei mit der Klassenimplementierung, falls Sie eine haben):

A::~A() {}

Das macht Ihren (virtuellen) Destruktor nicht inline und nicht vom Compiler generiert. (Sie können jederzeit Änderungen vornehmen, die Ihrem Code-Formatierungsstil besser entsprechen, z. B. einen Kopfzeilenkommentar zur Funktionsdefinition hinzufügen.)

0
JaMiT

Es ist auch möglich, dass Sie eine Nachricht erhalten

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'

wenn Sie vergessen, eine virtuelle Funktion einer Klasse FakeClass1 zu definieren, wenn Sie versuchen, einen Komponententest für eine andere Klasse SomeClass zu verknüpfen.

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

Und 

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

In diesem Fall schlage ich vor, dass Sie Ihre Fälschung für Klasse 1 noch einmal überprüfen. Sie werden wahrscheinlich feststellen, dass Sie vergessen haben, eine virtuelle Funktion ForgottenFunc in Ihrer falschen Klasse zu definieren.

0
Yuriy