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

357

При сборке моей программы на 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
рианг
источник
Какая функция выдает "неопределенную ссылку на vtable ..."?
Дж. Полфер
3
Я полностью пропустил, что в сообщении об ошибке указана функция. Это конструктор, поэтому я увидел имя своего класса и не установил соединение. Итак, конструктор бросает это. Я добавлю эту деталь в мой оригинальный пост.
RyanG
3
Если вы не перестроили файлы своего проекта после внесения значительных изменений (например, qmake -projectи затем qmake) для генерации нового Makefile, это вероятный источник ошибки при использовании Qt.
Дэвид С. Ранкин
@ DavidC.Rankin, еще одна проблема, связанная с Qt, заключается в том, что если файл с Q_OBJECTкопируется извне, но еще не является частью файла .pro, то, хотя он компилируется нормально, он не связывается. Мы должны добавить этот .h/.cppфайл в .pro файл, чтобы иметь возможность qmake.
Яммилинд

Ответы:

420

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

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

Александр хамес
источник
17
nm -C CGameModule.o | grep CGameModule::перечислит методы, которые определены, при условии, что вся ваша реализация класса идет в файл логического объекта. Вы можете сравнить это с тем, что определено как виртуальное, чтобы выяснить, что вы пропустили.
Трой Дэниелс
133
FFS, почему компилятор не проверяет это и не выводит сообщение об ошибке?
Ленар Хойт
20
Очевидно, что это может быть обнаружено только компоновщиком, а не компилятором.
Xoph
2
В моем случае у нас был абстрактный класс, у которого не было реализации деструктора. Мне пришлось поставить пустую реализацию ~ MyClass () {}
Шефи Гюрри
1
Вы можете получить такую ​​ошибку, когда объекты, которые вы пытаетесь связать, отсутствуют в архиве (файл libxyz.a): неопределенная ссылка на `vtable for objfilename '
Кемин Чжоу
162

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

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

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

Дэн
источник
23
Мне пришлось явно поместить тело моего пустого виртуального деструктора в файл определения (* .cc). Наличие этого в заголовке все еще дало мне ошибку.
PopcornKing
4
Обратите внимание, что как только я добавил виртуальный деструктор в файл реализации, gcc сообщил мне фактическую ошибку, которая была отсутствующим телом в другой функции.
настроение
1
@PopcornKing Я видел ту же проблему. Даже определение ~Destructor = default;в заголовочном файле не помогло. Есть ли задокументированная ошибка, поданная против gcc?
RD
это может быть другой проблемой, но моей проблемой было просто не иметь реализацию для не виртуального деструктора (переключался на уникальные / общие указатели и удалял его из исходного файла, но не имел «реализации» в заголовке )
svenevs
это решило мою проблему, добавив пустое тело {} для виртуального деструктора, чтобы избежать ошибки.
Богдан Ионица
56

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

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

рианг
источник
43
Вкратце: .cpp просто не был включен в сборку. Сообщение об ошибке действительно вводит в заблуждение.
Offirmo
67
Для пользователей Qt: вы можете получить эту же ошибку, если забыли завести заголовок.
Крис Морли
8
Я думаю, что вы должны принять ответ Александра Хамеса, хотя. Людям, которые ищут эту ошибку, скорее всего, понадобится его решение, а не ваше.
Тим
13
-1 Это может быть решением вашей проблемы, но это не ответ на оригинальный вопрос. Правильный ответ прост: вы не предоставили объектный файл с необходимыми символами. Почему вы не предоставили их, это другая история.
Уолтер
12
@Walter: На самом деле это был точный ответ, который я искал. Другие очевидны и, следовательно, бесполезны.
Эдгар Бонет
50

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

gluk47
источник
2
Я просто удалил целую папку со сборкой, она тоже работала.
Томаш Зато - Восстановить Монику
Это не уникально qmake, у меня было то же самое cmake. Частично проблема может заключаться в том, что оба инструмента имеют небольшую проблему с заголовочными файлами, которые не всегда могут вызвать перестроение при необходимости.
MSalters
2
Мысль "Rebuild" автоматически перезапускает qmake ... видимо, нет. Я сделал «Run qmake» по вашему предложению, затем «Rebuild», и это решило мою проблему.
Яно
45

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

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

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

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

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

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

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

Причина:

functionA определяется как чисто виртуальный, и его определение предоставляется в классе B. functionB определяется как виртуальное (NOT PURE VIRTUAL), поэтому он пытается найти свое определение в самом классе A, но вы предоставили его определение в классе B.

Решение:

  1. Сделайте функцию B чисто виртуальной (если у вас есть такое требование) virtual void functionB(parameters) =0; (это работает, проверено)
  2. Предоставить определение для функции B в самом классе A, сохраняя его как виртуальный. (Надеюсь, это работает, поскольку я не пробовал это)
Сриниваса Лаккараджу
источник
@ ilya1725 Предлагаемое вами изменение - это не только исправление форматирования и т. п., вы также меняете ответ, например, говоря, что класс C получен из B вместо A, а вы меняете второе решение. Это существенно меняет ответ. В этих случаях, пожалуйста, оставьте комментарий автору. Спасибо!
Фабио говорит восстановить Монику
@FabioTurati, от какого класса наследуется classC? Предложение не ясно. Кроме того, что означает «класс С содержит:»?
ilya1725
@ ilya1725 Этот ответ не очень понятен, и я не против его редактирования и улучшения. Я хочу сказать, что ваша редакция меняет смысл ответа, и это слишком радикальное изменение. Надеюсь, автор вмешается и уточнить, что он имел в виду (хотя он был неактивен в течение длительного времени).
Фабио говорит восстановить Монику
Спасибо! В моем случае у меня было только 2 класса в моей иерархии. Класс A объявлен чисто виртуальным методом. В объявлении класса B указано, что он переопределит этот метод, но я еще не написал определение переопределенного метода.
Ник Desaulniers
44

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

Будет
источник
Действительно, кажется, что сообщение немного отличается от обычного, undefined reference to {function/class/struct}когда есть virtualвещи, связанные с этим. Выкинул меня.
Кит М
30

Что такое vtable?

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

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

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

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

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

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

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

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

Элемент, выбранный 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() {}

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

JaMiT
источник
О браво! Для очень подробного и очень хорошего объяснения.
Дэвид С. Ранкин
Пришлось пролистать путь далеко, чтобы прочитать это. Имейте upvote для превосходного объяснения!
Томас
24

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

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

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

Шиталь шах
источник
18

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

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

virtual int foo(int x = 0);

И подкласс

int foo(int x) override;

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

virtual int foo(int x) = 0;

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

Филипп Томас
источник
12

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

Hazok
источник
1
Пожалуйста, добавьте еще немного описания к вашему ответу и возможному исправлению.
Мохит Джайн
1
Забвение ссылки может включать в себя забвение добавления в инструкции по сборке. В моем случае в моем cpp-файле все было полностью «определено», за исключением того, что я забыл добавить cpp-файл в исходный список (в моем CMakeLists.txt, но то же самое может случиться в других системах сборки, таких как в .pro-файле). В результате все скомпилировано, и тогда я получил ошибку во время ссылки ...
мудрец
@Mohit Jain Способ линковки в объектных файлах зависит от настроек среды и инструментов. К сожалению, конкретное исправление для одного человека может отличаться для другого (например, CMake против проприетарного инструмента против IDE против пр.)
Hazok
11

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

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

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

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

Юлиан Попа
источник
8

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

Такие как:

virtual void fooBar() = 0;

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

Виктор Хэгквист
источник
8

Хорошо, решение этого в том, что вы, возможно, пропустили определение. Смотрите пример ниже, чтобы избежать ошибки компилятора 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
  • Вы уверены, что CDasherComponent есть тело для деструктора? Это определенно не здесь - вопрос в том, находится ли он в файле .cc.
  • С точки зрения стиля, CDasherModuleследует явно определить его деструкторvirtual .
  • Похоже, CGameModuleимеет дополнительный } в конце (после}; // for the class ).
  • Связан ли CGameModuleон с библиотеками, которые определяют CDasherModuleи CDasherComponent?
Стивен
источник
- Да, CDasherComponent имеет тело деструктора в cpp. Я думал, что это было объявлено в .h, когда я отправил это. - Верно подмечено. - Это была дополнительная скобка, которую я добавил по ошибке при удалении документации. - Насколько я понимаю, да. Я модифицировал файл automake, который не написал, но я следовал шаблонам, которые работали для других классов с тем же шаблоном наследования из тех же классов, поэтому, если я не сделал глупую ошибку (вполне возможно) Я не думаю, что это все.
RyanG
@RyanG: попробуйте переместить все определения виртуальных функций в определение класса. Убедитесь, что они все есть, и посмотрите, изменится ли результат.
Стивен
5

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

virtual ~CDasherModule(){};
DevByStarlight
источник
5

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

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

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

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

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

void foo() {
  ...
}

Это следует прочитать

void B::foo() {
}
RedSpikeyThing
источник
3

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

Bretzelus
источник
3

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


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

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

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

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

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

C::Foo(){
   //bar
}

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

Натан Дэниелс
источник
Я не гуру C ++, но, похоже, это связано со смешением объявления и определения в одном файле с дополнительным файлом определения.
Терри Дж. Лорбер,
1
Когда определение функции находилось внутри определения вашего класса, оно неявно объявлялось «встроенным». В этот момент все ваши виртуальные функции были встроены. Когда вы переместили функцию за пределы определения класса, она больше не была «встроенной» функцией. Так как у вас была не встроенная виртуальная функция, ваш компилятор знал, куда создавать vtable. (Более подробное объяснение не
уместится
3

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

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

Q_OBJECT

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

#ifdef something

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

Так что удаление всех #ifdefs решило проблему.

Такого не было с Windows и VS 2013.

Даниэль Георгиев
источник
Закомментирование строки Q_OBJECT сделало мою простую сборку тестового приложения простой g++ *.cpp .... (Нужно было что-то быстрое и грязное, но qmake был полон горя.)
Натан Кидд
2

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

Джерри Миллер
источник
2

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

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

crw4096
источник
2

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

Стефан Монов
источник
2

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

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 tovtable для Foo 'collect2: error: ld вернул 1 состояние выхода

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

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

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

Адам Степняк
источник
1

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

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

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

Сидхарт Миддела
источник
странный. Если заголовок должен быть скопирован в другое место, система сборки должна обновить копию автоматически, как только будет изменен оригинал, и перед любым включением в другой файл. Если вам нужно сделать это вручную, вы ввернуты.
Offirmo
1

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

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

Я получил ошибку:

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

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

Кемин Чжоу
источник
0

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

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 при попытке связать модульный тест для другого класса 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

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

Юрий
источник
0

Я получил эту ошибку, когда я добавил второй класс в существующую пару источник / заголовок. Два заголовка класса в одном файле .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);
}

Снова, добавление новой пары источник / заголовок и сокращение / вставка дословно класса IconWithData туда "просто сработало".

AaronD
источник
0

Мой случай был глупым, у меня был дополнительный "после #includeпо ошибке, и угадайте, что?

undefined reference to vtable!

Я часами чесал голову и лицо, комментируя виртуальные функции, чтобы увидеть, изменилось ли что-нибудь, и, наконец, удалив лишнее ", все было исправлено! Такие вещи действительно должны приводить к ошибке компиляции, а не к ошибке ссылки.

Под дополнительным "я имею в виду:

#include "SomeHeader.h""
Бахрам Пурыйусфи
источник
0

В моем случае у меня был базовый класс с именем Person и два производных класса с именами Student и Professor.

Как была исправлена ​​моя программа: 1. Я сделал все функции в базовом классе Pure Virtual. 2. Я использовал все виртуальные деструкторы какdefault ones.

Гаурав Кумар
источник
-3

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

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

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

PointSet (const PointSet & pest, Parent * parent)

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

Витке
источник
7
Это не имело бы никакого значения, вы должны были иметь ошибку в другом месте и непреднамеренно исправить ее.
ММ