Невизначена посилання на 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.
iammilind

Відповіді:


420

У FAQ GCC є запис:

Рішення полягає в тому, щоб усі віртуальні методи, які не є чистими, визначені. Зауважте, що деструктор повинен бути визначений, навіть якщо він оголошений чисто-віртуальним [class.dtor] / 7.


17
nm -C CGameModule.o | grep CGameModule::перерахує визначені методи, припускаючи, що вся ваша реалізація класу переходить у файл логічного об’єкта. Ви можете порівняти це з тим, що визначено як віртуальне, щоб зрозуміти, що ви пропустили.
Troy Daniels

132
FFS, чому компілятор не перевіряє це і не друкує повідомлення про помилки?
Ленар Хойт

20
Очевидно, це може виявити лише лінкер, а не компілятор.
Xoph

2
У моєму випадку у нас був абстрактний клас, який не мав реалізації деструктора. Мені довелося поставити порожню реалізацію ~ MyClass () {}
Shefy Gur-arry

1
Ви можете отримати таку помилку, коли в архіві відсутні об’єкти, які ви намагаєтеся зв’язати (файл libxyz.a): невизначена посилання на `vtable for objfilename '
Kemin Zhou

162

Для чого варто забути тіло на віртуальному деструкторі генерує наступне:

невизначена посилання на `vtable для CYourClass '.

Я додаю примітку, оскільки повідомлення про помилку є оманливим. (Це було у версії 4.6.3 gcc.)


23
Мені довелося явно помістити тіло мого порожнього віртуального деструктора у файл визначення (* .cc). Маючи це в заголовку, все-таки дав мені помилку.
PopcornKing

4
Зауважте, що коли я додав віртуальний деструктор до файлу реалізації, то gcc повідомив мені фактичну помилку, яка була відсутнім тілом в іншій функції.
moodboom

1
@PopcornKing Я бачив ту саму проблему. Навіть визначення ~Destructor = default;файлу заголовка не допомогло. Чи є зареєстрована помилка, подана проти gcc?
РД

це може бути інша проблема, але моя проблема полягала в тому, що я не мав реалізації для невіртуального деструктора (переходив на унікальні / спільні покажчики та видаляв його з вихідного файлу, але не мав "реалізації" у заголовку )
svenevs

це вирішило мою проблему, додавши порожнє {} тіло для віртуального деструктора уникнуло помилки.
Богдан Іониця

56

Отже, я розібрався з проблемою, і це було поєднанням поганої логіки та не повністю ознайомлення зі світом автоматики / автоінструментів. Я додавав правильні файли до мого шаблону Makefile.am, але я не був впевнений, який крок у процесі збирання насправді створив сам makefile. Отже, я збирав старий makefile, який не мав уявлення про мої нові файли взагалі.

Дякуємо за відповіді та посилання на FAQ GCC. Я обов'язково прочитаю це, щоб уникнути виникнення цієї проблеми з реальної причини.


43
Якщо коротко: .cpp просто не був включений у збірку. Повідомлення про помилку дійсно вводить в оману.
Оффірмо

67
Для користувачів Qt: ви можете отримати цю ж помилку, якщо забудете закреслити заголовок.
Кріс Морльє

8
Я думаю, ти повинен прийняти відповідь Олександра Хамеса. Люди, які шукають цю помилку, швидше за все, потребують його рішення замість вашого.
Тім

13
-1 Це може бути вирішенням вашої проблеми, але це не відповідь на початкове запитання. Правильна відповідь полягає в тому, що ви не надали об'єктному файлу необхідні символи. Чому ви не змогли їх надати - це вже інша історія.
Вальтер

12
@Walter: Насправді це була точна відповідь, яку я шукав. Інші - очевидні, і, таким чином, безкорисні.
Едгар Бонет

50

Якщо ви використовуєте Qt, спробуйте відновити qmake. Якщо ця помилка в класі віджета, qmake, можливо, не зміг помітити, що vtable клас ui повинен бути відновлений. Це вирішило для мене проблему.


2
Я просто видалив цілу папку зі збіркою.
Томаш Зато - Відновіть Моніку

Це не властиво qmake, у мене було те саме cmake. Частина проблеми може полягати в тому, що обидва інструменти мають певну проблему з файлами заголовків, що може не завжди викликати відновлення при необхідності.
MSalters

2
Думав, що "Перебудувати" перезавантажити qmake автоматично ... очевидно, ні. Я зробив "Запуск qmake" за вашою пропозицією, потім "Перебудувати", і це вирішило мою проблему.
яно

45

Невизначене посилання на vtable також може виникнути через наступну ситуацію. Просто спробуйте:

Клас А Містить:

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

Клас В Містить:

  1. Визначення наведеної функціїA.
  2. Визначення наведеної функціїB.

Клас C містить: Тепер ви пишете клас C, в якому ви збираєтеся отримати його з класу A.

Тепер, якщо ви спробуєте компілювати, ви отримаєте Невизначене посилання на vtable для класу C як помилку.

Причина:

functionA визначається як чистий віртуальний і його визначення подано у класі В. functionB визначається як віртуальне (НЕ ЧИСТО ВІРТУАЛЬНЕ), тому він намагається знайти своє визначення в самому класі А, але ви надали його визначення в класі Б.

Рішення:

  1. Зробіть функцію B чистою віртуальною (якщо у вас є така вимога) virtual void functionB(parameters) =0; (Це працює, це випробувано)
  2. Надайте визначення для функціїB у самому класі А, зберігаючи його як віртуальний. (Сподіваюся, це працює, як я цього не пробував)

@ ilya1725 Ваша запропонована редакція - це не просто виправлення форматування тощо, ви також змінюєте відповідь, наприклад, кажучи, що клас C походить від B замість A, а ви змінюєте друге рішення. Це суттєво змінює відповідь. У цих випадках, будь ласка, залиште замість автора коментар. Дякую!
Фабіо каже, що відбудеться Моніка

@FabioTurati, який клас після цього успадковує classC? Присуд не зрозумілий. Також, яке значення "Клас C містить:"?
ilya1725

@ ilya1725 Ця відповідь не дуже зрозуміла, і я не проти її редагування та вдосконалення. Я говорю, що ваша редакція змінює значення відповіді, і це занадто різка зміна. Сподіваємось, автор вступить і уточнить, що він мав на увазі (хоча він давно був неактивний).
Фабіо каже, що повернеться до Моніки

Дякую! У моєму випадку я мав всього 2 класи у своїй ієрархії. Клас А оголошено чистим віртуальним методом. У декларації класу В було вказано, що це буде замінити цей метод, але я ще не написав визначення методу переопределення.
Нік Десольньє

44

Я просто отримав цю помилку, оскільки мій файл cpp не був у makefile.


Насправді, схоже, повідомлення дещо змінюється від звичайного, undefined reference to {function/class/struct}коли в цьому є virtualщось. Кинув мене.
Кіт М

30

Що таке а vtable ?

Можливо, буде корисно дізнатися, про що йдеться у повідомленні про помилку, перш ніж спробувати її виправити. Почну з високого рівня, а потім перейду до деяких деталей. Таким чином, люди можуть пропуститись вперед, як тільки їм буде зручно з розумінням рейтингів. … І зараз йде купа людей, що пропускають попереду прямо зараз. :) Для тих, хто тримається навколо:

В основному, vtable є найбільш поширеною реалізацією поліморфізму в C ++ . Коли використовуються vtables, кожен поліморфний клас має vtable десь у програмі; ви можете думати про це як про (прихованому) staticчлені даних класу. Кожен об'єкт поліморфного класу асоціюється з vtable для його найбільш похідного класу. Перевіряючи цю асоціацію, програма може відпрацьовувати свою поліморфну ​​магію. Важливий застереження: vtable - деталь реалізації. Це не передбачено стандартом C ++, хоча більшість (усі?) Компіляторів C ++ використовують vtables для реалізації поліморфної поведінки. Я представляю деталі або типовий, або розумний підхід. Компілятори дозволяють відступати від цього!

Кожен поліморфний об'єкт має (прихований) вказівник на vtable для найбільш похідного класу об'єкта (можливо, декілька покажчиків у складніших випадках). Подивившись на вказівник, програма може сказати, що таке «реальний» тип об’єкта (за винятком під час будівництва, але пропустимо цей особливий випадок). Наприклад, якщо об’єкт типу Aне вказує на vtable з A, то цей об'єкт насправді є суб’єктом чогось, що походить від A.

Назва «віртуальні таблиці» походить від « про irtual функції таблиці ». Це таблиця, в якій зберігаються вказівники на (віртуальні) функції. Компілятор вибирає свою умову щодо того, як складається таблиця; простий підхід полягає у проходженні віртуальних функцій у тому порядку, в якому вони оголошені у визначеннях класу. Коли викликується віртуальна функція, програма слідує за вказівником об'єкта на vtable, переходить до запису, пов'язаного з потрібною функцією, а потім використовує збережений вказівник функції для виклику правильної функції. Існують різні хитрощі, щоб зробити цю роботу, але я тут не буду вникати в них.

Де / коли vtableгенерується?

Компілятор автоматично створює vtable (іноді його називають "emit"). Компілятор може випустити vtable у кожному блоці перекладу, який бачить визначення поліморфного класу, але це, як правило, зайве зайве. Альтернативою ( використовуваною gcc , і, мабуть, іншими), є вибір єдиного блоку перекладу, в якому розмістити vtable, аналогічно тому, як ви вибрали б єдиний вихідний файл, у який слід помістити статичні дані класу класу. Якщо цей процес відбору не зможе вибрати будь-які одиниці перекладу, то 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() {}

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


О, браво! За дуже детальне та дуже вдале пояснення.
Девід К. Ранкін

Довелося прокручувати вниз шлях далеко, щоб прочитати це. Майте нагороду за відмінне пояснення!
Томас

24

Тут є багато спекуляцій у різних відповідях. Я нижче наведу досить мінімальний код, який відтворює цю помилку та поясню, чому вона виникає.

Досить мінімальний код для відтворення цієї помилки

IBase.hpp

#pragma once

class IBase {
    public:
        virtual void action() = 0;
};

Похідне.hpp

#pragma once

#include "IBase.hpp"

class Derived : public IBase {
    public:
        Derived(int a);
        void action() override;
};

Похідне.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

Пояснення

Зауважте, що вищевказаний код не потребує жодних віртуальних деструкторів, конструкторів чи будь-яких інших додаткових файлів для компіляції, щоб бути успішним (хоча ви повинні їх мати).

Спосіб зрозуміти цю помилку полягає в наступному: Linker шукає конструктора IBase. Це йому знадобиться для конструктора Derived. Однак, як похідні методи заміщення від IBase, до нього додається vtable, що посилається на IBase. Коли лінкер каже "невизначене посилання на vtable для IBase", це в основному означає, що Derived має посилання на vbase на 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

Це може статися досить легко, якщо ви забудете зв’язатись із файлом об'єкта, який має визначення.


1
Будь ласка, додайте ще трохи опису до своєї відповіді та можливого виправлення.
Мохіт Джайн

1
Забуття на посилання може включати забуття додати інструкції зі створення. У моєму випадку у моєму файлі cpp було все ідеально 'визначено', за винятком того, що я забув додати файл cpp до списку джерел (у моєму CMakeLists.txt, але те ж саме може статися і в інших системах побудови, таких як у файлі .pro). В результаті все складено, і тоді я отримав помилку під час посилання ...
мудрець

@Mohit Jain Як пов’язати файли об'єктів залежить від налаштування середовища та інструментів. На жаль, конкретний виправлення для однієї людини може бути іншим для іншого (наприклад, CMake vs патентований інструмент проти IDE проти тощо)
Hazok

11

Компілятор GNU C ++ повинен прийняти рішення, куди його поставити, якщо vtableу вас є визначення віртуальних функцій об'єкта, рознесеного по декількох одиницях компіляції (наприклад, деякі визначення віртуальних функцій об'єктів знаходяться у файлі .cpp, інші - в іншому. cpp-файл тощо).

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

Тепер, якщо ви з якоїсь причини забули надати визначення для першої віртуальної функції, оголошеної в об'єкті (або помилково забули додати скомпільований об'єкт на етапі зв’язку), ви отримаєте цю помилку.

В якості побічного ефекту, зауважте, що тільки для цієї конкретної віртуальної функції ви не отримаєте традиційну помилку лінкера, як у вас відсутня функція foo .


8

Не перекреслити пост, але. Якщо ви маєте справу зі спадщиною, другий хіт google був тим, що я пропустив, тобто. всі віртуальні методи повинні бути визначені.

Як от:

virtual void fooBar() = 0;

Докладні відомості див. У програмі Answare C ++ Undefined Reference to vtable and nasledstvo . Щойно зрозумів, що це вже було сказано вище, але чорт може допомогти комусь.


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, коли я розмістив це. - Належним чином відзначено. - Це була додаткова дужка, яку я помилково додав, знімаючи документацію. - Наскільки я розумію, так. Я видозмінював файл автоматичної автоматики, який я не писав, але я стежу за шаблонами, які працювали для інших класів з тією ж схемою успадкування з тих самих класів, так що, якщо я не зробив дурну помилку (цілком можливо) , Я не думаю, що це все.
RyanG

@RyanG: спробуйте перемістити всі визначення віртуальних функцій у визначення класу. Переконайтесь, що вони всі там, і подивіться, чи зміниться результат.
Стівен

5

Можливо, фактор, що відсутній, відсутній віртуальний деструктор?

virtual ~CDasherModule(){};

5

Це був перший результат пошуку для мене, тому я подумав додати ще одну річ, щоб перевірити: переконайтесь, що визначення віртуальних функцій насправді є у класі. У моєму випадку у мене було таке:

Файл заголовка:

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

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

і в моєму .cc-файлі:

void foo() {
  ...
}

Це слід прочитати

void B::foo() {
}


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 ++, але, схоже, це пов'язано зі змішуванням декларації та визначення в одному файлі з додатковим файлом визначення.
Terry G Lorber

1
Коли визначення функції знаходилось всередині вашого визначення класу, воно неявно було оголошено "вбудованим". У цей момент усі ваші віртуальні функції були вкреслені. Коли ви перемістили функцію поза визначенням класу, вона більше не була функцією "вбудована". Оскільки у вас була не вбудована віртуальна функція, ваш компілятор знав, куди випустити vtable. (Більш ретельне пояснення не впишеться у коментар.)
JaMiT

3

Тому я використовував Qt з компілятором Windows XP і MinGW, і ця річ зводила мене з розуму.

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

Q_OBJECT

Видалення всіх функцій, що роблять віртуальними, явними, і все, що ви здогадуєтесь, не працює. Нарешті я почав видаляти рядок за рядком, і виявилося, що маю

#ifdef something

Навколо файлу. Навіть коли #ifdef був істинним, moc-файл не створювався.

Тож видалення всіх #ifdefs виправило проблему.

З Windows і VS 2013 цього не було.


Коментуючи рядок Q_OBJECT, мій простий додаток для тестування було створено просто g++ *.cpp .... (Потрібно було щось швидке і брудне, але qmake був повний горя.)
Nathan Kidd

2

Якщо все інше не вдається, шукайте дублювання. Мене неправильно спрямували явні початкові посилання на конструктори та деструктори, поки я не прочитав посилання в іншому дописі. Це будь-який невирішений метод. У моєму випадку я думав, що я замінив декларацію, яка використовувала char * xml як параметр, на одну, використовуючи зайво проблемний const char * xml, але натомість я створив нову та залишив іншу на місці.


2

Існує багато згаданих можливостей для спричинення цієї помилки, і я впевнений, що багато з них викликають помилку. У моєму випадку було інше визначення того ж класу через дублювання вихідного файлу. Цей файл був складений, але не пов'язаний, тому лінкер скаржився на неможливість його знайти.

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


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: У функції vtable main': main.cpp:(.text+0x1a): undefined reference toдля Foo 'collection2: error: ld повернув 1 статус виходу

Це траплялось із моєї невпинної, оскільки:

  1. Vtable створюється для кожного класу під час компіляції

  2. Linker не має доступу до vtable, який знаходиться у Foo.o


1

Я отримав цю помилку в наступному сценарії

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

Щоб виправити це, перш ніж будувати, переконайтесь, що ви видалили файл заголовка (до якого ви внесли зміни) у загальну / включає каталог. Також переконайтеся, що ви змінили свій файл файлів, щоб вмістити / додати новий. Виконуючи ці дії, компілятор / посилання більше не скаржиться.


дивно. Якщо заголовок потрібно скопіювати десь в іншому місці, система збирання повинна оновити копію автоматично, як тільки буде змінено оригінал, і перед будь-яким включенням в інший файл. Якщо вам доведеться це зробити вручну, ви прикручуєтесь.
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 дослідно туди "просто працювало".


0

Моя справа була дурною, у мене з’явились додаткові "після #includeпомилки та здогадуєтесь що?

undefined reference to vtable!

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

Додатково "я маю на увазі:

#include "SomeHeader.h""

0

У моєму випадку у мене був базовий клас на ім’я Особа та два похідні класи на ім'я Студент та Професор.

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


-3

Я отримав цю помилку лише тому, що ім'я аргументу конструктора відрізнялося у файлі заголовка та у файлі реалізації. Підпис конструктора є

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

і те, що я написав у виконанні, почалося з

PointSet (const PointSet & pest, Parent * parent)

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


7
Це взагалі не мало б значення, ви, мабуть, мали помилку в іншому місці і ненавмисно виправили її.
ММ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.