Неопределенная ссылка на vtable - PullRequest
286 голосов
/ 17 июня 2010

При сборке моей программы на 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

Ответы [ 27 ]

363 голосов
/ 18 июня 2010

В GCC FAQ есть запись:

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

146 голосов
/ 27 февраля 2013

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

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

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

50 голосов
/ 21 июня 2010

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

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

42 голосов
/ 13 января 2015

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

39 голосов
/ 12 февраля 2014

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

39 голосов
/ 27 апреля 2013

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

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

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

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

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

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

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

Причина:

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

Решение:

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

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

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

virtual int foo(int x = 0);

И у подкласса было

int foo(int x) override;

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

virtual int foo(int x) = 0;

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

15 голосов
/ 10 мая 2016

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

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

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 в качестве библиотеки.

11 голосов
/ 19 января 2012

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

11 голосов
/ 11 февраля 2015

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

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

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

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

...