Niezdefiniowane odniesienie do vtable

357

Podczas budowania mojego programu C ++ pojawia się komunikat o błędzie

niezdefiniowane odniesienie do „vtable ...

Co jest przyczyną tego problemu? Jak to naprawić?


Zdarza się, że pojawia się błąd dla następującego kodu (klasa, o której mowa, to CGameModule.) I przez całe życie nie mogę zrozumieć, na czym polega problem. Na początku myślałem, że ma to związek z zapomnieniem nadania funkcji wirtualnej ciała, ale o ile rozumiem, wszystko jest tutaj. Łańcuch dziedziczenia jest trochę długi, ale tutaj znajduje się powiązany kod źródłowy. Nie jestem pewien, jakie inne informacje powinienem podać.

Uwaga: Wydaje się, że w tym miejscu występuje błąd.

Mój kod:

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

Dziedziczy po ...

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

Który dziedziczy po ...

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
RyanG
źródło
Która funkcja rzuca „niezdefiniowane odniesienie do vtable ...”?
J. Polfer,
3
Całkowicie spóźniłem się, że komunikat o błędzie określa funkcję. Zdarza się, że jest to konstruktor, więc zobaczyłem nazwę mojej klasy i nie nawiązałem połączenia. Konstruktor to rzuca. Dodam ten szczegół do mojego oryginalnego postu.
RyanG,
3
Jeśli nie przebudowałeś plików projektu po wprowadzeniu znaczących zmian (np. qmake -projectA następnie qmake) w celu wygenerowania nowego Makefile, jest to prawdopodobne źródło błędu podczas korzystania z Qt.
David C. Rankin
@ DavidC.Rankin, innym problemem związanym z Qt jest to, że jeśli plik z Q_OBJECTjest kopiowany zewnętrznie, ale nie jest jeszcze częścią pliku .pro, to mimo że kompiluje się dobrze, nie łączy się. Musimy dodać ten .h/.cppplik do pliku .pro, aby móc qmake.
iammilind

Odpowiedzi:

420

W GCC FAQ jest wpis:

Rozwiązaniem jest upewnienie się, że wszystkie wirtualne metody, które nie są czyste, są zdefiniowane. Zauważ, że destruktor musi być zdefiniowany, nawet jeśli jest zadeklarowany jako czysto wirtualny [class.dtor] / 7.

Alexandre Hamez
źródło
17
nm -C CGameModule.o | grep CGameModule::wyświetli listę zdefiniowanych metod, zakładając, że cała implementacja klasy trafi do pliku obiektu logicznego. Możesz porównać to z wirtualnym, aby dowiedzieć się, co przegapiłeś.
Troy Daniels,
132
FFS, dlaczego kompilator tego nie sprawdza i nie wyświetla komunikatu o błędzie?
Lenar Hoyt
20
Oczywiście może to odkryć tylko linker, a nie kompilator.
Xoph
2
W moim przypadku mieliśmy klasę abstrakcyjną, która nie miała implementacji destruktora. Musiałem umieścić pustą implementację ~ MyClass () {}
Shefy Gur-ary
1
Możesz otrzymać taki błąd, gdy brakuje obiektów, które próbujesz połączyć w archiwum (plik libxyz.a): niezdefiniowane odniesienie do `vtable for objfilename '
Kemin Zhou,
162

Za to, co jest warte, zapomnienie ciała na wirtualnym destruktorze generuje:

niezdefiniowane odniesienie do `vtable for CYourClass '.

Dodam notatkę, ponieważ komunikat o błędzie jest zwodniczy. (To było z wersją gcc 4.6.3.)

Dan
źródło
23
Musiałem jawnie umieścić ciało mojego pustego wirtualnego destruktora w pliku definicji (* .cc). Trzymanie go w nagłówku wciąż dawało mi błąd.
PopcornKing
4
Zauważ, że kiedy dodałem wirtualny destruktor do pliku implementacji, gcc powiedział mi rzeczywisty błąd, którym było brakujące ciało w innej funkcji.
moodboom
1
@PopcornKing Widziałem ten sam problem. Nawet zdefiniowanie ~Destructor = default;w pliku nagłówkowym nie pomogło. Czy istnieje udokumentowany błąd zgłoszony przeciwko gcc?
RD
może to być inny problem, ale moim problemem było po prostu brak implementacji dla nie-wirtualnego destruktora (przełączanie się na unikalne / wspólne wskaźniki i usunięcie go z pliku źródłowego, ale brak nagłówka „implementacja” w nagłówku )
svenevs
to rozwiązało mój problem, dodanie pustego ciała {} wirtualnego destruktora pozwoliło uniknąć błędu.
Bogdan Ionitza
56

Więc wymyśliłem problem i było to połączenie złej logiki i nieznajomości świata automake / autotools. Dodawałem prawidłowe pliki do mojego szablonu Makefile.am, ale nie byłem pewien, który krok w naszym procesie kompilacji faktycznie utworzył sam plik makefile. Kompilowałem więc ze starym makefile, który nie miał pojęcia o moich nowych plikach.

Dziękujemy za odpowiedzi i link do GCC FAQ. Przeczytam to, aby uniknąć tego problemu z prawdziwego powodu.

RyanG
źródło
43
W skrócie: .cpp po prostu nie został uwzględniony w kompilacji. Komunikat o błędzie naprawdę wprowadza w błąd.
Offirmo
67
Dla użytkowników Qt: ten sam błąd można uzyskać, jeśli zapomnisz o mocowaniu nagłówka.
Chris Morlier,
8
Myślę jednak, że powinieneś zaakceptować odpowiedź Alexandre'a Hameza. Ludzie szukający tego błędu najprawdopodobniej potrzebowaliby jego rozwiązania zamiast twojego.
Tim
13
-1 To może być rozwiązanie twojego problemu, ale nie jest to odpowiedź na pierwotne pytanie. Prawidłowa odpowiedź to po prostu to, że nie dostarczyłeś pliku obiektowego z wymaganymi symbolami. Dlaczego nie dostarczyłeś ich, to inna historia.
Walter
12
@ Walter: Właściwie to była dokładnie ta odpowiedź, której szukałem. Inne są oczywiste, a zatem nieprzydatne.
Edgar Bonet
50

Jeśli używasz Qt, spróbuj ponownie uruchomić qmake. Jeśli ten błąd występuje w klasie widgetu, qmake mógł nie zauważyć, że vtable klasy interfejsu użytkownika powinien zostać zregenerowany. To rozwiązało problem.

gluk47
źródło
2
Właśnie usunąłem cały folder z kompilacją, która również działała.
Tomáš Zato - Przywróć Monikę
To nie jest wyjątkowe qmake, miałem to samo z cmake. Częścią problemu może być to, że oba narzędzia mają niewielki problem z plikami nagłówkowymi, co może nie zawsze powodować przebudowę w razie potrzeby.
MSalters
2
Myśl „Odbuduj” ponownie qmake automatycznie ... najwyraźniej nie. Zrobiłem „Uruchom qmake” według twojej sugestii, a następnie „Przebuduj” i naprawiłem mój problem.
yano
45

Nieokreślone odwołanie do vtable może również wystąpić z powodu następującej sytuacji. Po prostu spróbuj tego:

Klasa A zawiera:

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

Klasa B zawiera:

  1. Definicja powyższej funkcji A.
  2. Definicja powyższej funkcji B.

Klasa C zawiera: Teraz piszesz klasę C, w której zamierzasz ją wyprowadzić z klasy A.

Teraz, jeśli spróbujesz skompilować, otrzymasz jako niezdefiniowane odniesienie do vtable dla klasy C.

Powód:

functionAjest zdefiniowany jako czysty wirtualny, a jego definicja jest podana w klasie B. functionBjest zdefiniowany jako wirtualny (NIE CZYSTY WIRTUALNY), więc próbuje znaleźć swoją definicję w samej klasie A, ale podałeś jej definicję w klasie B.

Rozwiązanie:

  1. Ustaw funkcję B jako czystą wirtualną (jeśli masz takie wymagania) virtual void functionB(parameters) =0; (to działa, jest testowana)
  2. Podaj definicję funkcji B w samej klasie A, zachowując ją jako wirtualną. (Mam nadzieję, że to działa, ponieważ nie próbowałem tego)
Srinivasa Lakkaraju
źródło
@ ilya1725 Sugerowana edycja nie polega tylko na poprawieniu formatowania itp., zmieniasz również odpowiedź, na przykład mówiąc, że klasa C pochodzi od B zamiast A i zmieniasz drugie rozwiązanie. To znacznie zmienia odpowiedź. W takich przypadkach pozostaw komentarz autorowi. Dziękuję Ci!
Fabio mówi Przywróć Monikę
@FabioTurati, którą klasę dziedziczy classC? To zdanie nie jest jasne. Co też oznacza „klasa C zawiera:”?
ilya1725
@ ilya1725 Ta odpowiedź nie jest bardzo jasna i nie jestem przeciwny jej edycji i ulepszaniu. Mówię o tym, że twoja edycja zmienia znaczenie odpowiedzi, a to zbyt drastyczna zmiana. Mamy nadzieję, że autor wkroczy i wyjaśni, co miał na myśli (chociaż był nieaktywny przez długi czas).
Fabio mówi Przywróć Monikę
Dzięki! W moim przypadku miałem tylko 2 klasy w mojej hierarchii. Klasa A zadeklarowała metodę czysto wirtualną. Deklaracja klasy B oświadczyła, że ​​zastąpi tę metodę, ale nie napisałem jeszcze definicji metody zastępującej.
Nick Desaulniers
44

Po prostu dostałem ten błąd, ponieważ mojego pliku CPP nie ma w pliku makefile.

Będzie
źródło
Rzeczywiście wydaje się, że komunikat zmienia się nieznacznie w porównaniu do zwykłego, undefined reference to {function/class/struct}gdy w grę wchodzą virtualrzeczy. Rzucił mnie.
Keith M
30

Co jest vtable?

Przed próbą naprawienia warto wiedzieć, o czym mówi komunikat o błędzie. Zacznę od wysokiego poziomu, a potem dopracuję więcej szczegółów. W ten sposób ludzie mogą przejść do przodu, gdy tylko poczują się dobrze ze zrozumieniem tabel. … I jest teraz grupa ludzi skaczących przed siebie. :) Dla osób pozostających w pobliżu:

Vtable jest w zasadzie najczęstszą implementacją polimorfizmu w C ++ . Kiedy używane są vtables, każda klasa polimorficzna ma gdzieś w programie vtable; możesz myśleć o tym jako o (ukrytym) staticelemencie danych w klasie. Każdy obiekt klasy polimorficznej jest powiązany z tabelą vt dla swojej najbardziej pochodnej klasy. Sprawdzając to powiązanie, program może działać na swoją magię polimorficzną. Ważne zastrzeżenie: vtable to szczegół implementacji. Nie jest to wymagane przez standard C ++, nawet jeśli większość (wszystkich?) Kompilatorów C ++ używa vtables do implementacji zachowania polimorficznego. Przedstawione przeze mnie szczegóły są typowymi lub rozsądnymi podejściami. Kompilatory mogą się od tego różnić!

Każdy obiekt polimorficzny ma (ukryty) wskaźnik do tabeli vt dla najbardziej pochodnej klasy obiektu (być może wielu wskaźników, w bardziej złożonych przypadkach). Patrząc na wskaźnik, program może stwierdzić, jaki jest „prawdziwy” typ obiektu (z wyjątkiem budowy, ale pomińmy ten szczególny przypadek). Na przykład, jeśli obiekt typu Anie wskazuje na vtable A, wówczas ten obiekt jest w rzeczywistości podobiektem czegoś, z czego pochodzi A.

Nazwa „vtable” pochodzi od „ v rzeczywistej tabeli funkcji ”. Jest to tabela przechowująca wskaźniki do funkcji (wirtualnych). Kompilator wybiera konwencję dotyczącą układu tabeli; prostym podejściem jest przejście przez funkcje wirtualne w kolejności, w jakiej są zadeklarowane w definicjach klas. Po wywołaniu funkcji wirtualnej program podąża za wskaźnikiem obiektu do tabeli vt, przechodzi do pozycji powiązanej z żądaną funkcją, a następnie używa wskaźnika funkcji zapisanej do wywołania poprawnej funkcji. Jest wiele różnych sztuczek, żeby to zrobić, ale nie będę się tutaj zajmował.

Gdzie / kiedy jest vtablegenerowany?

Vtable jest automatycznie generowany (czasem nazywany „emitowanym”) przez kompilator. Kompilator może emitować vtable w każdej jednostce tłumaczenia, która widzi definicję klasy polimorficznej, ale zwykle byłoby to niepotrzebne przesadne działanie. Alternatywą ( używaną przez gcc i prawdopodobnie przez innych) jest wybranie pojedynczej jednostki tłumaczeniowej, w której umieści się vtable, podobnie jak w przypadku wybrania pojedynczego pliku źródłowego, w którym można umieścić statyczne elementy danych klasy. Jeśli podczas tego procesu wyboru nie zostaną wybrane żadne jednostki tłumaczeniowe, tabela vt staje się niezdefiniowanym odwołaniem. Stąd błąd, którego przesłanie nie jest wprawdzie szczególnie jasne.

Podobnie, jeśli proces selekcji powoduje wybór jednostki tłumaczeniowej, ale plik obiektowy nie jest udostępniany konsolidatorowi, wówczas tabela vt staje się niezdefiniowanym odwołaniem. Niestety komunikat o błędzie może być w tym przypadku jeszcze mniej wyraźny niż w przypadku, gdy proces wyboru nie powiódł się. (Dzięki autorom odpowiedzi, którzy wspominali o tej możliwości. Prawdopodobnie w innym przypadku o tym zapomniałbym.)

Proces selekcji używany przez gcc ma sens, jeśli zaczniemy od tradycji poświęcania (pojedynczego) pliku źródłowego każdej klasie, która potrzebuje jednej do jego implementacji. Byłoby miło wyemitować vtable podczas kompilacji tego pliku źródłowego. Nazwijmy to naszym celem. Proces selekcji musi jednak działać, nawet jeśli nie przestrzega się tej tradycji. Zamiast szukać implementacji całej klasy, spójrzmy na implementację konkretnego członka klasy. Jeśli przestrzega się tradycji - a ten członek jest rzeczywiście wdrażany - osiąga to cel.

Element wybrany przez gcc (i potencjalnie przez inne kompilatory) jest pierwszą nieliniową funkcją wirtualną, która nie jest czysto wirtualna. Jeśli należysz do tłumu, który deklaruje konstruktory i destruktory przed innymi funkcjami składowymi, to ten destruktor ma dużą szansę zostać wybrany. (Pamiętasz, jak zrobić wirtualny destruktor, prawda?) Istnieją wyjątki; Spodziewałbym się, że najczęstszymi wyjątkami są sytuacje, gdy dla destruktora wprowadzona jest definicja wbudowana i gdy żądany jest domyślny destruktor (użycie „ = default”).

Bystry może zauważyć, że klasa polimorficzna może dostarczać wbudowane definicje wszystkich swoich funkcji wirtualnych. Czy to nie powoduje niepowodzenia procesu selekcji? Robi to w starszych kompilatorach. Czytałem, że najnowsze kompilatory rozwiązały tę sytuację, ale nie znam odpowiednich numerów wersji. Mógłbym spróbować to sprawdzić, ale łatwiej jest albo go zakodować, albo poczekać, aż kompilator narzeka.

Podsumowując, istnieją trzy kluczowe przyczyny błędu „niezdefiniowane odwołanie do vtable”:

  1. Funkcja członka nie ma swojej definicji.
  2. Plik obiektowy nie jest łączony.
  3. Wszystkie funkcje wirtualne mają wbudowane definicje.

Przyczyny te same w sobie nie są wystarczające, aby samodzielnie spowodować błąd. Zamiast tego należy rozwiązać problem. Nie oczekuj, że celowe utworzenie jednej z tych sytuacji z pewnością spowoduje ten błąd; są inne wymagania. Spodziewaj się, że rozwiązanie tych sytuacji rozwiąże ten błąd.

(OK, liczba 3 mogła być wystarczająca, gdy zadano pytanie).

Jak naprawić błąd?

Witaj z powrotem ludzi, którzy skaczą do przodu! :)

  1. Spójrz na swoją definicję klasy. Znajdź pierwszą nieliniową funkcję wirtualną, która nie jest czysto wirtualna (nie „ = 0”) i której definicję podasz (nie „ = default”).
    • Jeśli nie ma takiej funkcji, spróbuj zmodyfikować swoją klasę, aby była taka. (Prawdopodobnie błąd został rozwiązany.)
    • Zobacz także odpowiedź Philipa Thomasa na ostrzeżenie.
  2. Znajdź definicję tej funkcji. Jeśli go brakuje, dodaj go! (Prawdopodobnie błąd został rozwiązany.)
  3. Sprawdź polecenie link. Jeśli nie wspomina o pliku obiektowym z definicją tej funkcji, napraw to! (Prawdopodobnie błąd został rozwiązany.)
  4. Powtarzaj kroki 2 i 3 dla każdej funkcji wirtualnej, a następnie dla każdej funkcji innej niż wirtualna, aż błąd zostanie rozwiązany. Jeśli nadal utkniesz, powtórz dla każdego statycznego elementu danych.

Przykład
Szczegółowe informacje na temat tego, co należy zrobić, mogą się różnić, a czasem rozgałęzić na osobne pytania (np. Co to jest niezdefiniowany błąd odniesienia / nierozwiązany błąd symbolu zewnętrznego i jak to naprawić? ). Podam jednak przykład tego, co należy zrobić w konkretnym przypadku, który może być kłopotliwy dla nowszych programistów.

Krok 1 wspomina o modyfikacji klasy, tak aby miała ona funkcję określonego typu. Jeśli opis tej funkcji przeszedł ci przez głowę, być może znajdujesz się w sytuacji, którą zamierzam rozwiązać. Pamiętaj, że jest to sposób na osiągnięcie celu; nie jest to jedyny sposób, a w twojej konkretnej sytuacji mogą być łatwiejsze sposoby. Zadzwońmy do twojej klasy A. Czy twój destruktor jest zadeklarowany (w definicji klasy) jako jeden z nich?

virtual ~A() = default;

lub

virtual ~A() {}

? Jeśli tak, dwa kroki zmienią twój destruktor w pożądany typ funkcji. Najpierw zmień tę linię na

virtual ~A();

Po drugie, wstaw następujący wiersz do pliku źródłowego, który jest częścią twojego projektu (najlepiej plik z implementacją klasy, jeśli taki masz):

A::~A() {}

To sprawia, że ​​Twój (wirtualny) destruktor nie jest wbudowany i nie jest generowany przez kompilator. (Zachęcamy do modyfikowania elementów, aby lepiej pasowały do ​​stylu formatowania kodu, takich jak dodawanie komentarza nagłówka do definicji funkcji).

JaMiT
źródło
Och, brawo! Dla bardzo szczegółowego i bardzo dokładnego wyjaśnienia.
David C. Rankin,
Musiałem przewinąć w dół do daleko, aby to przeczytać. Głosuj za doskonałym wyjaśnieniem!
Thomas
24

W różnych odpowiedziach dzieje się wiele spekulacji. Poniżej podam dość minimalny kod, który odtwarza ten błąd i wyjaśniam, dlaczego się pojawia.

Dość minimalny kod do odtworzenia tego błędu

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

Możesz skompilować to za pomocą GCC w następujący sposób:

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

Możesz teraz odtworzyć błąd, usuwając go = 0z pliku IBase.hpp. Otrzymuję ten błąd:

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

Wyjaśnienie

Zauważ, że powyższy kod nie wymaga żadnych wirtualnych niszczycieli, konstruktorów ani żadnych innych dodatkowych plików, aby kompilacja zakończyła się powodzeniem (chociaż powinieneś je mieć).

Sposób zrozumienia tego błędu jest następujący: Linker szuka konstruktora IBazy. Będzie to potrzebne dla konstruktora Derived. Jednak, ponieważ pochodna zastępuje metody z IBazy, dołączono do niej vtable, która będzie odwoływać się do IBazy. Kiedy linker mówi „niezdefiniowane odwołanie do vtable dla IBase”, oznacza to po prostu, że Derived ma vtable odwołanie do IBase, ale nie może znaleźć żadnego skompilowanego kodu obiektowego IBase do wyszukania. Najważniejsze jest to, że klasa IBase ma deklaracje bez implementacji. Oznacza to, że metoda w IBase jest zadeklarowana jako wirtualna, ale zapomnieliśmy oznaczyć ją jako czystą wirtualną LUB podać jej definicję.

Porada na rozstanie

Jeśli wszystko inne zawiedzie, jednym ze sposobów debugowania tego błędu jest zbudowanie minimalnego programu, który się kompiluje, a następnie zmiana go, aby uzyskać pożądany stan. W międzyczasie kompiluj dalej, aby zobaczyć, kiedy zacznie się nie udać.

Uwaga dotycząca systemu kompilacji ROS i Catkina

Jeśli kompilujesz powyżej zestawu klas w ROS przy użyciu systemu budowania catkin, będziesz potrzebować następujących wierszy w CMakeLists.txt:

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

Pierwszy wiersz w zasadzie mówi, że chcemy stworzyć plik wykonywalny o nazwie myclass, a kod do jego zbudowania można znaleźć następujące pliki. Jeden z tych plików powinien mieć main (). Zauważ, że nie musisz określać plików .hpp w dowolnym miejscu w CMakeLists.txt. Nie musisz też określać Derived.cpp jako biblioteki.

Shital Shah
źródło
18

Właśnie natrafiłem na inną przyczynę tego błędu, którą możesz sprawdzić.

Klasa podstawowa zdefiniowała czystą funkcję wirtualną jako:

virtual int foo(int x = 0);

I podklasa miała

int foo(int x) override;

Problem polegał na tym, że literówka "=0"powinna znajdować się poza nawiasami:

virtual int foo(int x) = 0;

Tak więc, jeśli przewijasz tak daleko, prawdopodobnie nie znalazłeś odpowiedzi - jest to coś innego do sprawdzenia.

Philip Thomas
źródło
12

Może się to zdarzyć dość łatwo, jeśli zapomnisz połączyć z plikiem obiektowym, który ma definicję.

Hazok
źródło
1
Dodaj więcej opisu do swojej odpowiedzi i ewentualnej poprawki.
Mohit Jain
1
Zapomnienie o połączeniu może obejmować zapomnienie o dodaniu instrukcji budowania. W moim przypadku mój plik cpp miał wszystko „dokładnie” zdefiniowane, z wyjątkiem tego, że zapomniałem dodać plik cpp do listy źródłowej (w moim CMakeLists.txt, ale to samo może się zdarzyć w innych systemach kompilacji, takich jak plik .pro). W rezultacie wszystko się skompilowało, a potem dostałem błąd przy łączeniu ...
mędrzec
@Mohit Jain Sposób łączenia w plikach obiektów zależy od konfiguracji środowiska i narzędzi. Niestety konkretna poprawka dla jednej osoby może być inna dla innej (np. CMake vs. zastrzeżone narzędzie vs IDE vs itp.)
Hazok
11

Kompilator GNU C ++ musi podjąć decyzję, gdzie umieścić, vtablew przypadku gdy masz definicję funkcji wirtualnych obiektu rozmieszczonych w wielu jednostkach kompilacji (np. Niektóre definicje funkcji wirtualnych obiektów znajdują się w pliku .cpp, inne w innym. plik cpp itd.).

Kompilator wybiera umieszczenie tego vtablesamego miejsca, w którym zdefiniowana jest pierwsza zadeklarowana funkcja wirtualna.

Teraz, jeśli z jakiegoś powodu zapomniałeś podać definicję pierwszej funkcji wirtualnej zadeklarowanej w obiekcie (lub omyłkowo zapomniałeś dodać skompilowany obiekt w fazie łączenia), pojawi się ten błąd.

Jako efekt uboczny należy pamiętać, że tylko w przypadku tej konkretnej funkcji wirtualnej nie pojawi się tradycyjny błąd linkera, tak jak w przypadku braku funkcji foo .

Iulian Popa
źródło
8

Nie przechodzić przez pocztę, ale. Jeśli masz do czynienia z dziedziczeniem, drugim hitem w Google było to, czego mi brakowało, tj. wszystkie metody wirtualne powinny być zdefiniowane.

Jak na przykład:

virtual void fooBar() = 0;

Aby uzyskać szczegółowe informacje, zobacz answare C ++ Undefined Odniesienie do vtable i dziedziczenia . Właśnie zdałem sobie sprawę, że już wspomniano powyżej, ale do cholery może to komuś pomóc.

Victor Häggqvist
źródło
8

Ok, rozwiązaniem tego może być brak definicji. Zobacz poniższy przykład, aby uniknąć błędu kompilatora 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
{

}
Sammy
źródło
7
  • Czy na pewno masz CDasherComponentciało dla destruktora? Na pewno go tu nie ma - pytanie brzmi, czy znajduje się w pliku .cc.
  • Z perspektywy stylu CDasherModulepowinien wyraźnie zdefiniować swój destruktor virtual.
  • Wygląda na to, że CGameModulema dodatkowy } na końcu (po}; // for the class ).
  • Czy CGameModulejest powiązany z bibliotekami, które definiują CDasherModulei CDasherComponent?
Stephen
źródło
- Tak, CDasherComponent ma ciało destruktora w cpp. Myślałem, że został zadeklarowany w .h, kiedy to opublikowałem. - Zanotowano. - To był dodatkowy nawias dodany przez pomyłkę podczas usuwania dokumentacji. - O ile rozumiem, tak. Modyfikowałem plik automake, którego nie napisałem, ale podążam za wzorami, które działały dla innych klas z tym samym wzorcem dziedziczenia z tych samych klas, więc chyba, że ​​popełniłem głupi błąd (całkowicie możliwe) , Nie sądzę, że o to chodzi.
RyanG,
@ Ryan: spróbuj przenieść wszystkie definicje funkcji wirtualnych do definicji klasy. Upewnij się, że wszyscy tam są i sprawdź, czy wynik się zmieni.
Stephen
5

Być może brak wirtualnego destruktora jest czynnikiem?

virtual ~CDasherModule(){};
DevByStarlight
źródło
5

To był dla mnie pierwszy wynik wyszukiwania, więc pomyślałem, że dodam jeszcze jedną rzecz do sprawdzenia: upewnij się, że definicja funkcji wirtualnych jest rzeczywiście w klasie. W moim przypadku miałem to:

Plik nagłówka:

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

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

i w moim pliku .cc:

void foo() {
  ...
}

To powinno przeczytać

void B::foo() {
}
RedSpikeyThing
źródło
3

Może nie. Zdecydowanie ~CDasherModule() {}brakuje.

Bretzelus
źródło
3

Tak wiele odpowiedzi tutaj, ale żadna z nich nie obejmowała mojego problemu. Miałem następujące:


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

I w innym pliku (oczywiście zawartym w kompilacji i linkowaniu)

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

Cóż, to nie zadziałało i mam błąd, o którym wszyscy mówią. Aby go rozwiązać, musiałem przenieść rzeczywistą definicję Foo z deklaracji klasy jako takiej:

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

C::Foo(){
   //bar
}

Nie jestem guru C ++, więc nie mogę wyjaśnić, dlaczego jest to bardziej poprawne, ale rozwiązało to problem.

Nathan Daniels
źródło
Nie jestem guru C ++, ale wydaje się, że jest to związane z mieszaniem deklaracji i definicji w tym samym pliku z dodatkowym plikiem definicji.
Terry G Lorber
1
Gdy definicja funkcji znajdowała się w definicji klasy, została domyślnie zadeklarowana jako „wbudowana”. W tym momencie wszystkie funkcje wirtualne zostały wprowadzone. Gdy funkcja została przeniesiona poza definicję klasy, nie była już funkcją „wbudowaną”. Ponieważ posiadałeś nieliniową funkcję wirtualną, twój kompilator wiedział, gdzie wysłać vtable. (Dokładniejsze wyjaśnienie nie zmieści się w komentarzu).
JaMiT
3

Używałem Qt z Windows XP i kompilatorem MinGW i to doprowadzało mnie do szału.

Zasadniczo plik moc_xxx.cpp został wygenerowany pusty, nawet gdy zostałem dodany

Q_OBJECT

Usunięcie wszystkiego, co czyni funkcje wirtualnymi, jawnymi i cokolwiek zgadniesz, nie działa. W końcu zacząłem usuwać wiersz po wierszu i okazało się, że tak

#ifdef something

Wokół pliku. Nawet gdy #ifdef był prawdziwy plik moc nie został wygenerowany.

Usunięcie wszystkich #ifdefs naprawiło problem.

To nie działo się z Windows i VS 2013.

Daniel Georgiev
źródło
Komentowanie wiersza Q_OBJECT sprawiło, że moja prosta aplikacja testowa została zbudowana przy pomocy zwykłego g++ *.cpp .... (Potrzebował czegoś szybkiego i brudnego, ale qmake był pełen żalu.)
Nathan Kidd
2

Jeśli wszystko inne zawiedzie, poszukaj duplikacji. Zostałem źle skierowany przez jawne początkowe odniesienie do konstruktorów i destruktorów, dopóki nie przeczytałem odniesienia w innym poście. To jakakolwiek nierozwiązana metoda. W moim przypadku myślałem, że zastąpiłem deklarację, która używała char * xml jako parametru, jedną z niepotrzebnie kłopotliwej const char * xml, ale zamiast tego stworzyłem nową i pozostawiłem drugą na miejscu.

Jerry Miller
źródło
2

Wspomniano wiele możliwości spowodowania tego błędu i jestem pewien, że wiele z nich powoduje błąd. W moim przypadku istniała inna definicja tej samej klasy z powodu duplikacji pliku źródłowego. Ten plik został skompilowany, ale nie połączony, więc linker narzekał na niemożność jego znalezienia.

Podsumowując, powiedziałbym, że jeśli patrzysz na klasę wystarczająco długo i nie widzisz, jaki potencjalny problem ze składnią może być przyczyną, poszukaj problemów z kompilacją, takich jak brakujący plik lub duplikat pliku.

crw4096
źródło
2

W moim przypadku używam Qt i zdefiniowałem QObjectpodklasę w foo.cpp(nie .h) pliku. Poprawka miała zostać dodana #include "foo.moc"na końcu foo.cpp.

Stefan Monov
źródło
2

Myślę, że warto również wspomnieć, że otrzymasz wiadomość, gdy spróbujesz połączyć się z obiektem dowolnej klasy, która ma co najmniej jedną metodę wirtualną i linker nie może znaleźć pliku. Na przykład:

Foo.hpp:

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

Foo.cpp:

#include "Foo.hpp"

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

Kompilowany z:

g++ Foo.cpp -c

I main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Skompilowane i powiązane z:

g++ main.cpp -o main

Podaje nasz ulubiony błąd:

/tmp/cclKnW0g.o: W funkcji main': main.cpp:(.text+0x1a): undefined reference tovtable dla Foo 'collect2: błąd: ld zwrócił 1 status wyjścia

To zdarzyło się z mojego niezrozumiałego, ponieważ:

  1. Vtable jest tworzony dla każdej klasy w czasie kompilacji

  2. Linker nie ma dostępu do vtable, który znajduje się w Foo.o

Adam Stepniak
źródło
1

Wystąpił ten błąd w następującym scenariuszu

Rozważ przypadek, w którym zdefiniowałeś implementację funkcji członkowskich klasy w samym pliku nagłówkowym. Ten plik nagłówka jest wyeksportowanym nagłówkiem (innymi słowy, można go skopiować do jakiegoś wspólnego pliku / include bezpośrednio w bazie kodu). Teraz zdecydowałeś się oddzielić implementację funkcji członka do pliku .cpp. Po rozdzieleniu / przeniesieniu implementacji do pliku .cpp plik nagłówka zawiera teraz tylko prototypy funkcji składowych w klasie. Po wykonaniu powyższych zmian, jeśli zbudujesz bazę kodu, może pojawić się błąd „niezdefiniowane odniesienie do 'vtable ...”.

Aby to naprawić, przed budowaniem upewnij się, że usunąłeś plik nagłówka (do którego wprowadziłeś zmiany) we wspólnym katalogu / include. Upewnij się także, że zmieniłeś swój plik makefile, aby dostosować / dodać nowy plik .o, który jest zbudowany z nowego właśnie utworzonego pliku .cpp. Po wykonaniu tych kroków kompilator / linker nie będzie już narzekał.

Sidharth Middela
źródło
dziwne. Jeśli nagłówek ma zostać skopiowany w innym miejscu, system kompilacji powinien aktualizować kopię automatycznie, jak tylko oryginał zostanie zmodyfikowany, a przed jakimkolwiek dołączeniem do innego pliku. Jeśli musisz to zrobić ręcznie, jesteś przykręcony.
Offirmo
1

Wystąpił tego rodzaju błąd w sytuacjach, gdy próbowałem połączyć się z obiektem, gdy dostałem błąd make, który uniemożliwił dodanie obiektu do archiwum.

Powiedzmy, że mam libXYZ.a, który powinien mieć bioseq.o w int, ale nie ma.

Mam błąd:

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

Jest to zupełnie inne niż wszystkie powyższe. Nazwałbym ten brakujący obiekt problemem archiwizacji.

Kemin Zhou
źródło
0

Możliwe jest również, że otrzymasz wiadomość typu „jak”

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'

jeśli zapomnisz zdefiniować funkcję wirtualną klasy FakeClass1, gdy próbujesz połączyć test jednostkowy z inną klasą SomeClass.

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

I

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

W takim przypadku sugeruję, abyś jeszcze raz sprawdził swoją fałszywość dla klasy 1. Prawdopodobnie okaże się, że zapomniałeś zdefiniować funkcję wirtualną ForgottenFuncw swojej fałszywej klasie.

Jurij
źródło
0

Wystąpił ten błąd, gdy dodałem drugą klasę do istniejącej pary źródło / nagłówek. Dwa nagłówki klas w tym samym pliku .h oraz definicje funkcji dla dwóch klas w tym samym pliku .cpp.

Zrobiłem to już wcześniej, z lekcjami, które mają ściśle ze sobą współpracować, ale najwyraźniej tym razem coś mnie nie lubiło. Nadal nie wiem co, ale podział na jedną klasę na jednostkę kompilacji naprawił to.


Nieudana próba:

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

Ponownie dodając nową parę źródło / nagłówek i wycinając / wklejając dosłownie klasę IconWithData „po prostu działało”.

AaronD
źródło
0

Mój przypadek był głupi jeden, miałem dodatkową "po #includeprzez pomyłkę i wiecie co?

undefined reference to vtable!

Godzinami drapię się po palcach po głowie i twarzy, komentując funkcje wirtualne, aby zobaczyć, czy coś się zmieniło, i wreszcie usuwając dodatkowe " , wszystko zostało naprawione! Tego rodzaju rzeczy naprawdę muszą skutkować błędem kompilacji, a nie błędem łącza.

Przez dodatkowe "rozumiem:

#include "SomeHeader.h""
Bahram Pouryousefi
źródło
0

W moim przypadku miałem klasę podstawową o nazwie Osoba i dwie klasy pochodne o nazwie Student i Professor.

Jak naprawiłem mój program, to: 1. Wykonałem wszystkie funkcje w klasie bazowej Pure Virtual. 2. Użyłem wszystkich wirtualnych destruktorów jakodefault ones.

Gaurav Kumar
źródło
-3

Wystąpił ten błąd tylko dlatego, że nazwa argumentu konstruktora różniła się w pliku nagłówkowym i pliku implementacji. Sygnatura konstruktora to

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

i od tego zacząłem pisać

PointSet (const PointSet & pest, Parent * parent)

dlatego przypadkowo zastąpiłem „pset” słowem „szkodnik”. Kompilator narzekał na ten jeden i dwa inne konstruktory, w których nie wystąpił żaden błąd. Używam g ++ w wersji 4.9.1 pod Ubuntu. A zdefiniowanie wirtualnego destruktora w tej klasie pochodnej nie miało znaczenia (jest zdefiniowane w klasie bazowej). Nigdy nie znalazłbym tego błędu, gdybym nie wkleił ciał konstruktorów w pliku nagłówkowym, definiując je w klasie.

vitke
źródło
7
To nie miałoby żadnego znaczenia, musiałeś mieć błąd gdzie indziej i niechcący go naprawić.
MM