Undefined ссылка на vtable

При сборке моей программы C++ я получаю сообщение об ошибке

неопределенная ссылка на 'vtable...

В чем причина этой проблемы? Как мне это исправить?


Так получилось, что я получаю сообщение об ошибке для следующего кода (рассматриваемый класс - CGameModule.), И я не могу на всю жизнь понять, в чем проблема. Сначала я думал, что это связано с забвением придания виртуальной функции тела, но, насколько я понимаю, все здесь. Цепочка наследования немного длинна, но вот соответствующий исходный код. Я не уверен, какую другую информацию я должен предоставить.

Примечание: конструктор, где эта ошибка происходит, казалось бы.

Мой код:

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

Наследует от...

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

Который наследует от....

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

Ответы

Ответ 1

Итак, я выяснил эту проблему, и это была комбинация плохой логики и не была полностью знакома с миром automake/autotools. Я добавлял правильные файлы в мой шаблон Makefile.am, но я не был уверен, какой шаг в нашем процессе сборки фактически создал сам makefile. Итак, я собирался со старым make файлом, который вообще не знал о моих новых файлах.

Спасибо за ответы и ссылку на FAQ GCC. Я обязательно прочитаю это, чтобы избежать этой проблемы, возникшей по реальной причине.

Ответ 2

Вопросы GCC содержит запись:

Решение состоит в том, чтобы определить, что все виртуальные методы, которые не являются чистыми, определены. Обратите внимание, что деструктор должен быть определен, даже если он объявлен pure-virtual [class.dtor]/7.

Ответ 3

Для чего стоит забыть тело виртуального деструктора, генерирует следующее:

undefined ссылка на `vtable для CYourClass '.

Я добавляю заметку, потому что сообщение об ошибке обманчиво. (Это было с gcc версии 4.6.3.)

Ответ 4

Если вы используете Qt, попробуйте перезапустить qmake. Если эта ошибка находится в классе виджетов, qmake, возможно, не заметил, что vi-класс vtable должен быть регенерирован. Это исправило проблему для меня.

Ответ 5

Я просто получил эту ошибку, потому что мой файл cpp не был в make файле.

Ответ 6

Undefined ссылка на vtable может возникнуть из-за следующей ситуации. Просто попробуйте следующее:

Класс A Содержит:

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

Класс B Содержит:

  • Определение для указанной выше функции A.
  • Определение для указанной выше функции B.

Класс C Содержит: Теперь вы пишете класс C, в котором вы собираетесь извлечь его из класса A.

Теперь, если вы попытаетесь скомпилировать, вы получите ссылку Undefined на vtable для класса C как ошибку.

Причина:

functionA определяется как чистый виртуальный, и его определение предоставляется в классе B. functionB определяется как виртуальный (НЕ ЧИСТЫЙ ВИРТУАЛЬНЫЙ), поэтому он пытается найти свое определение в самом классе A, но вы указали его определение в классе B.

Решение:

  • Сделать функцию B чистой виртуальной (если у вас есть такая потребность) virtual void functionB(parameters) =0; (Это работает, оно протестировано)
  • Предоставить определение для функции B в самом классе A, сохраняя его как виртуальное. (Надеюсь, что он работает, поскольку я не пробовал это).

Ответ 7

Здесь много спекуляций в разных ответах. Ниже я приведу довольно минимальный код, который воспроизводит эту ошибку, и объясню, почему она возникает.

Довольно минимальный код для воспроизведения этой ошибки

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

Вы можете скомпилировать это с помощью GCC следующим образом:

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

Теперь вы можете воспроизвести ошибку, удалив = 0 в IBase.hpp. Я получаю эту ошибку:

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

объяснение

Обратите внимание, что приведенный выше код не требует каких-либо виртуальных деструкторов, конструкторов или любых других дополнительных файлов для успешной компиляции (хотя они должны быть у вас).

Способ понять эту ошибку следующим образом: Линкер ищет конструктор IBase. Это понадобится для конструктора Derived. Однако поскольку Derived переопределяет методы из IBase, к нему прикреплен vtable, который будет ссылаться на IBase. Когда компоновщик говорит "неопределенная ссылка на vtable для IBase", это в основном означает, что Derived имеет vtable ссылку на IBase, но он не может найти какой-либо скомпилированный объектный код IBase для поиска. Итак, суть в том, что у класса IBase есть объявления без реализаций. Это означает, что метод в IBase объявлен как виртуальный, но мы забыли пометить его как чисто виртуальный ИЛИ предоставить его определение.

Прощание Совет

Если ничего не помогает, то одним из способов отладки этой ошибки является создание минимальной программы, которая выполняет компиляцию, а затем продолжает изменять ее, чтобы она достигла желаемого состояния. В промежутке, продолжайте компиляцию, чтобы увидеть, когда он начинает давать сбой.

Обратите внимание на систему сборки ROS и Catkin

Если вы компилировали вышеуказанный набор классов в ROS с использованием системы сборки catkin, то вам понадобятся следующие строки в CMakeLists.txt:

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

Первая строка в основном говорит, что мы хотим создать исполняемый файл с именем myclass, а код для его сборки можно найти в следующих файлах. Один из этих файлов должен иметь main(). Обратите внимание, что вам не нужно указывать файлы .hpp где-либо в CMakeLists.txt. Также вам не нужно указывать Derived.cpp в качестве библиотеки.

Ответ 8

Я просто столкнулся с другой причиной этой ошибки, которую вы можете проверить.

Базовый класс определил чистую виртуальную функцию как:

virtual int foo(int x = 0);

И подкласс имел

int foo(int x) override;

Проблема заключалась в том, что символ "=0" должен был находиться вне круглой скобки:

virtual int foo(int x) = 0;

Итак, если вы прокручиваете это далеко вниз, вы, вероятно, не нашли ответа - это еще что-то проверить.

Ответ 9

Это может произойти довольно легко, если вы забудете связать с объектным файлом, который имеет определение.

Ответ 10

Компилятор GNU С++ должен принять решение о том, где поставить vtable, если у вас есть определение виртуальных функций объекта, распределенного по нескольким единицам компиляции (например, некоторые из определений виртуальных функций объектов находятся в a. cpp файлы других в другой .cpp файл и т.д.).

Компилятор решает поставить vtable в том же месте, где определена первая объявленная виртуальная функция.

Теперь, если вы почему-то забыли предоставить определение для этой первой виртуальной функции, объявленной в объекте (или по ошибке забыли добавить скомпилированный объект на этапе компоновки), вы получите эту ошибку.

Как побочный эффект, обратите внимание, что только для этой конкретной виртуальной функции вы не получите традиционную ошибку компоновщика, как будто вам не хватает функции foo.

Ответ 11

Хорошо, решение этого состоит в том, что вы, возможно, пропустили определение. См. Пример ниже, чтобы избежать ошибки компилятора 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
{

}

Ответ 12

Не перекреститься, но. Если вы имеете дело с наследованием, второй хит Google был тем, что я пропустил, т.е. все виртуальные методы должны быть определены.

Например:

virtual void fooBar() = 0;

Подробнее см. answare С++ Undefined Ссылка на vtable и наследование. Просто понял, что это уже упоминалось выше, но, черт возьми, это может помочь кому-то.

Ответ 13

  • Вы уверены, что CDasherComponent имеет тело для деструктора? Это определенно не здесь - вопрос в том, находится ли он в файле .cc.
  • С точки зрения стиля CDasherModule должен явно определить свой деструктор virtual.
  • Похоже, что CGameModule имеет дополнительный } в конце (после }; // for the class).
  • Связано ли CGameModule с библиотеками, которые определяют CDasherModule и CDasherComponent?

Ответ 14

Возможно, отсутствующий виртуальный деструктор является способствующим фактором?

virtual ~CDasherModule(){};

Ответ 15

Это был первый результат поиска для меня, поэтому я подумал, что добавлю еще одну вещь, чтобы проверить: убедитесь, что определение виртуальных функций действительно принадлежит классу. В моем случае у меня было это:

Заголовочный файл:

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

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

и в моем файле .cc:

void foo() {
  ...
}

Это должно читать

void B::foo() {
}

Ответ 16

Не возможно. Определенно ~CDasherModule() {} отсутствует.

Ответ 17

Здесь так много ответов, но ни один из них, похоже, не охватил мою проблему. У меня было следующее:


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

И в другом файле (включая, конечно, компиляцию и ссылку)

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

Ну, это не сработало, и я получил ошибку, о которой все говорят. Чтобы решить эту проблему, мне пришлось перенести фактическое определение Foo из объявления класса как такового:

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

C::Foo(){
   //bar
}

Я не гуру С++, поэтому я не могу объяснить, почему это более правильно, но он решил проблему для меня.

Ответ 18

Итак, я использовал Qt с компилятором Windows XP и MinGW, и эта вещь сводила меня с ума.

В основном moc_xxx.cpp был сгенерирован пустым, даже когда я был добавлен

Q_OBJECT

Удаление всего, что делает виртуальные, явные и все, что вы предполагаете, не работает. Наконец, я начал удалять строки за строкой, и оказалось, что у меня был

#ifdef something

Вокруг файла. Даже когда #ifdef был истинным, moc файл не был сгенерирован.

Поэтому удаление всех #ifdefs устраняет проблему.

Эта вещь не происходила с Windows и VS 2013.

Ответ 19

Если все остальное не работает, найдите дублирование. Я был неправильно настроен по явной начальной ссылке на конструкторы и деструкторы, пока не прочитал ссылку в другом сообщении. Это любой неразрешенный метод. В моем случае я думал, что заменил объявление, в котором использовался char * xml, как параметр с использованием ненужного проблемного const char * xml, но вместо этого я создал новый и оставил другой на своем месте.

Ответ 20

Существует много возможностей для этой ошибки, и я уверен, что многие из них вызывают ошибку. В моем случае было другое определение того же класса из-за дублирования исходного файла. Этот файл был скомпилирован, но не связан, поэтому компоновщик жаловался на то, что не смог его найти.

Подводя итог, я бы сказал, что если вы достаточно долго смотрели на класс и не могли понять, что может вызвать его синтаксическая проблема, посмотрите на проблемы с сборкой, такие как отсутствующий файл или дублированный файл.

Ответ 21

В моем случае я использую Qt и определил подкласс QObject в файле foo.cpp (not .h). Исправление заключалось в том, чтобы добавить #include "foo.moc" в конец foo.cpp.

Ответ 22

Я думаю, также стоит упомянуть, что вы также получите сообщение, когда попытаетесь связать объект любого класса, который имеет хотя бы один виртуальный метод, и компоновщик не может найти файл. Например:

Foo.hpp:

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

foo.cpp:

#include "Foo.hpp"

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

Составлено с:

g++ Foo.cpp -c

И main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Составлено и связано с:

g++ main.cpp -o main

Дает нашу любимую ошибку:

/tmp/cclKnW0g.o: в функции main': main.cpp:(.text+0x1a): undefined reference to vtable для Foo' collect2: error: ld вернул 1 состояние выхода

Это произошло из-за моего непонимания:

  1. Vtable создается для каждого класса во время компиляции

  2. Линкер не имеет доступа к vtable, который есть в Foo.o

Ответ 23

Я получил эту ошибку в следующем сценарии

Рассмотрим случай, когда вы определили реализацию функций-членов класса в самом файле заголовка. Этот заголовочный файл является экспортированным заголовком (другими словами, он может быть скопирован в некоторые общие/включить непосредственно в вашу базу кода). Теперь вы решили разделить реализацию функций-членов на .cpp файл. После того, как вы разделили/переместили реализацию на .cpp, в файле заголовка теперь есть только прототипы функций-членов внутри класса. После вышеуказанных изменений, если вы создадите свою кодовую базу, вы можете получить ссылку "undefined на ошибку" vtable... ".

Чтобы исправить это, перед созданием убедитесь, что вы удалили файл заголовка (к которому вы вносили изменения) в общий каталог /include. Также убедитесь, что вы изменили свой файл makefile, чтобы разместить/добавить новый .o файл, который был создан из нового .cpp файла, который вы только что создали. Когда вы выполните эти шаги, компилятор/компоновщик больше не будет жаловаться.

Ответ 24

Я получил этот тип ошибки в ситуациях, когда я пытался связать объект, когда у меня появилась ошибка make, которая помешала добавить объект в архив.

Скажем, у меня есть libXYZ.a, который должен иметь bioseq.o в int, но это не так.

Появилась ошибка:

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

Это отличное от всего вышеперечисленного. Я бы назвал этот недостающий объект в проблеме архива.

Ответ 25

Также возможно, что вы получите сообщение типа

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'

если вы забудете определить виртуальную функцию класса FakeClass1, когда вы пытаетесь связать unit test для другого класса SomeClass.

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

И

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

В этом случае я предлагаю вам снова проверить свою подделку для класса 1. Вероятно, вы обнаружите, что вы, возможно, забыли определить виртуальную функцию ForgottenFunc в вашем поддельном классе.

Ответ 26

Я получил эту ошибку, когда добавил второй класс к существующей паре источников/заголовков. Два заголовка класса в одном файле .h и определения функций для двух классов в одном файле .cpp.

Я сделал это успешно раньше, с классами, которые предназначены для совместной работы, но на этот раз мне что-то не понравилось. Все еще не знаю, что, но разбивая их на один класс на единицу компиляции, он исправил его.


Ошибка:

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

Опять же, добавив новую пару source/header и вырезая/вставляя класс IconWithData дословно, там "только что сработал".

Ответ 27

Что такое vtable?

Может быть полезно узнать, о чем идет речь в сообщении об ошибке, прежде чем пытаться это исправить. Я начну с высокого уровня, а затем рассмотрю некоторые детали. Таким образом, люди могут пропустить вперед, как только они освоятся с пониманием vtables. & hellip; и тут идет куча людей, пропускающих прямо сейчас. :) Для тех, кто слоняется поблизости:

Vtable в основном является наиболее распространенной реализацией полиморфизма вC++. Когда используются vtables, у каждого полиморфного класса есть vtable где-нибудь в программе; Вы можете думать об этом как о (скрытом) static элементе данных класса. Каждый объект полиморфного класса связан с vtable для его самого производного класса. Проверяя эту связь, программа может проявить свою полиморфную магию. Важное предостережение: vtable - это деталь реализации. Это не предписано стандартом C++, хотя большинство (все?) Компиляторов C++ используют vtables для реализации полиморфного поведения. Детали, которые я представляю, являются либо типичными, либо разумными подходами. Компиляторы могут отклоняться от этого!

Каждый полиморфный объект имеет (скрытый) указатель на виртуальную таблицу для самого производного класса объекта (возможно, несколько указателей, в более сложных случаях). Посмотрев на указатель, программа может определить, что представляет собой "настоящий" тип объекта (кроме как во время конструирования, но давайте пропустим этот особый случай). Например, если объект типа A не указывает на виртуальную таблицу A, то этот объект на самом деле является подобъектом чего-то, полученного из A.

Название "vtable" происходит от "v виртуальной функции таблицы". Это таблица, в которой хранятся указатели на (виртуальные) функции. Компилятор выбирает свое соглашение о том, как таблица составлена; простой подход состоит в том, чтобы пройти через виртуальные функции в порядке, в котором они объявлены в определениях классов. Когда вызывается виртуальная функция, программа следует за указателем объекта на vtable, переходит к записи, связанной с требуемой функцией, затем использует указатель сохраненной функции для вызова правильной функции. Существуют различные приемы для создания этой работы, но я не буду вдаваться в подробности.

Где/когда генерируется vtable?

Vtable автоматически генерируется (иногда называемый "испускаемым") компилятором. Компилятор может генерировать виртуальную таблицу в каждом модуле перевода, который видит определение полиморфного класса, но это, как правило, будет излишним излишним. Альтернатива (используемая gcc и, вероятно, другими) - это выбрать одну единицу перевода, в которую нужно поместить виртуальную таблицу, подобно тому, как вы выбираете один исходный файл, в который помещается статический класс члены данных. Если этот процесс выбора не может выбрать какие-либо единицы перевода, тогда vtable становится неопределенной ссылкой. Отсюда и ошибка, сообщение которой, по общему признанию, не особенно ясно.

Точно так же, если процесс выбора выбирает единицу перевода, но этот объектный файл не предоставляется компоновщику, тогда vtable становится неопределенной ссылкой. К сожалению, сообщение об ошибке может быть даже менее четким в этом случае, чем в случае неудачного процесса выбора. (Спасибо ответчикам, которые упомянули об этой возможности. В противном случае я бы забыл об этом.)

Процесс выбора, используемый gcc, имеет смысл, если мы начнем с традиции выделять (один) исходный файл каждому классу, который нужен для его реализации. Было бы неплохо создать vtable при компиляции этого исходного файла. Позвольте назвать это нашей целью. Однако процесс отбора должен работать, даже если эта традиция не будет соблюдена. Поэтому вместо того, чтобы искать реализацию всего класса, давайте посмотрим на реализацию конкретного члена класса. Если традиции соблюдены & ndash; и если этот участник действительно реализован & ndash; тогда это достигает цели.

Элемент, выбранный gcc (и, возможно, другими компиляторами), является первой не встроенной виртуальной функцией, которая не является чисто виртуальной. Если вы являетесь частью толпы, которая объявляет конструкторы и деструкторы перед другими функциями-членами, то этот деструктор имеет хорошие шансы быть выбранным. (Вы не забыли сделать виртуальный деструктор, верно?) Есть исключения; Я ожидаю, что наиболее распространенными исключениями являются случаи, когда для деструктора предоставляется встроенное определение и когда запрашивается деструктор по умолчанию (с использованием "= default").

Проницательный может заметить, что полиморфному классу разрешено предоставлять встроенные определения для всех его виртуальных функций. Разве это не приводит к сбою процесса выбора? Это происходит в старых компиляторах. Я читал, что последние компиляторы справились с этой ситуацией, но я не знаю соответствующих номеров версий. Я мог бы попытаться найти это, но было бы проще либо кодировать его, либо подождать, пока компилятор пожалуется.

Таким образом, существует три основных причины ошибки "неопределенная ссылка на vtable":

  1. В функции-члене отсутствует определение.
  2. Объектный файл не связан.
  3. Все виртуальные функции имеют встроенные определения.

Эти причины сами по себе недостаточны, чтобы вызвать ошибку самостоятельно. Скорее, это то, к чему вы обратились бы, чтобы устранить ошибку. Не ожидайте, что намеренное создание одной из этих ситуаций определенно приведет к этой ошибке; Есть и другие требования. Ожидайте, что разрешение этих ситуаций разрешит эту ошибку.

(ОК, номер 3, возможно, было достаточно, когда был задан этот вопрос.)

Как исправить ошибку?

Добро пожаловать обратно, люди пропускают вперед! :)

  1. Посмотрите на определение вашего класса. Найдите первую не встроенную виртуальную функцию, которая не является чисто виртуальной (не "= 0") и определение которой вы предоставляете (не "= default").
    • Если такой функции нет, попробуйте изменить класс так, чтобы он был. (Возможно, ошибка устранена.)
    • См. также ответ филиппа Томаса для уточнения.
  2. Найдите определение для этой функции. Если он отсутствует, добавьте его! (Возможно, ошибка устранена.)
  3. Проверьте вашу команду ссылки. Если он не упоминает объектный файл с определением этой функции, исправьте это! (Возможно, ошибка устранена.)
  4. Повторите шаги 2 и 3 для каждой виртуальной функции, затем для каждой не виртуальной функции, пока ошибка не будет устранена. Если вы все еще застряли, повторите для каждого члена статических данных.

Пример
Детали того, что нужно делать, могут различаться и иногда разбиваться на отдельные вопросы (например, Что такое неопределенная ссылка/неразрешенная ошибка внешнего символа и как ее исправить?). Тем не менее, я приведу пример того, что делать в конкретном случае, который может сбить с толку новых программистов.

Шаг 1 упоминает изменение вашего класса, чтобы он имел функцию определенного типа. Если описание этой функции перешло вам голову, вы можете оказаться в ситуации, которую я собираюсь рассмотреть. Имейте в виду, что это способ достижения цели; это не единственный способ, и в вашей конкретной ситуации легко могут быть лучшие способы. Позвольте позвонить вашему классу A. Ваш деструктор объявлен (в определении вашего класса) как

virtual ~A() = default;

или

virtual ~A() {}

? Если это так, два шага изменит ваш деструктор на тот тип функции, который мы хотим. Сначала измените эту строку на

virtual ~A();

Во-вторых, поместите следующую строку в исходный файл, который является частью вашего проекта (предпочтительно файл с реализацией класса, если он у вас есть):

A::~A() {}

Это делает ваш (виртуальный) деструктор не встроенным и не генерируемым компилятором. (Не стесняйтесь изменять вещи, чтобы они лучше соответствовали вашему стилю форматирования кода, например, добавив комментарий заголовка в определение функции.)

Ответ 28

Я получил эту ошибку только потому, что имя аргумента конструктора отличалось в файле заголовка и в файле реализации. Подпись конструктора

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

и то, что я написал в реализации, началось с

PointSet (const PointSet & pest, Parent * parent)

таким образом я случайно заменил "pset" на "вредитель". Компилятор жаловался на этот один и два других конструктора, в которых вообще не было ошибок. Я использую g++ версии 4.9.1 под Ubuntu. И определение виртуального деструктора в этом производном классе не имело никакого значения (оно определено в базовом классе). Я бы никогда не нашел эту ошибку, если бы я не вставлял тела конструкторов в заголовочный файл, тем самым определяя их в классе.