Referensi yang tidak terdefinisi ke vtable

357

Saat membuat program C ++ saya, saya menerima pesan kesalahan

referensi tidak terdefinisi ke 'vtable ...

Apa penyebab masalah ini? Bagaimana saya memperbaikinya?


Kebetulan saya mendapatkan kesalahan untuk kode berikut (Kelas yang dimaksud adalah CGameModule.) Dan saya tidak bisa seumur hidup saya mengerti apa masalahnya. Pada awalnya, saya pikir itu terkait dengan lupa untuk memberikan fungsi virtual pada tubuh, tetapi sejauh yang saya mengerti, semuanya ada di sini. Rantai pewarisan agak panjang, tetapi di sini adalah kode sumber terkait. Saya tidak yakin informasi apa yang harus saya berikan.

Catatan: Konstruktor adalah tempat kesalahan ini terjadi, sepertinya.

Kode saya:

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

Warisan dari ...

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

Yang mewarisi dari ....

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
sumber
Fungsi mana yang melempar "referensi ke vtable ..." yang tidak ditentukan?
J. Polfer
3
Saya benar-benar merindukan bahwa pesan kesalahan menentukan suatu fungsi. Kebetulan itu adalah konstruktor, jadi saya melihat nama kelas saya dan tidak membuat koneksi. Jadi, konstruktor melempar ini. Saya akan menambahkan detail itu ke posting asli saya.
RyanG
3
Jika Anda belum membangun kembali file proyek Anda setelah membuat perubahan signifikan (mis. qmake -projectDan kemudian qmake) untuk menghasilkan yang baru Makefile, itu kemungkinan merupakan sumber kesalahan ketika menggunakan Qt.
David C. Rankin
@ DavidC.Rankin, masalah terkait Qt lainnya adalah bahwa jika file dengan Q_OBJECTdisalin secara eksternal, tetapi belum menjadi bagian dari file .pro, maka meskipun dikompilasi dengan baik, ia tidak terhubung. Kita harus menambahkan .h/.cppfile itu ke file .pro untuk dapat qmake.
iammilind

Jawaban:

420

The GCC FAQ memiliki entri di atasnya:

Solusinya adalah memastikan bahwa semua metode virtual yang tidak murni didefinisikan. Perhatikan bahwa destruktor harus didefinisikan bahkan jika itu dinyatakan [virtual-virtual] [class.dtor] / 7.

Alexandre Hamez
sumber
17
nm -C CGameModule.o | grep CGameModule::akan mencantumkan metode yang didefinisikan, dengan asumsi seluruh implementasi kelas Anda masuk ke file objek logis. Anda dapat membandingkannya dengan apa yang didefinisikan sebagai virtual untuk mengetahui apa yang Anda lewatkan.
Troy Daniels
133
FFS, mengapa kompiler tidak memeriksanya dan mencetak pesan kesalahan?
Lenar Hoyt
20
Jelas, ini hanya dapat ditemukan oleh linker, bukan kompiler.
Xoph
2
Dalam kasus saya, kami memiliki kelas abstrak yang tidak memiliki implementasi destruktor. Saya harus meletakkan implementasi kosong ~ MyClass () {}
Shefy Gur-ary
1
Anda bisa mendapatkan kesalahan seperti ini ketika benda yang Anda coba tautkan tidak ada dalam arsip (file libxyz.a): referensi tidak terdefinisi untuk `vtable for objfilename '
Kemin Zhou
162

Untuk apa nilainya, melupakan tubuh pada destruktor virtual menghasilkan yang berikut:

referensi tidak terdefinisi untuk `vtable for CYourClass '.

Saya menambahkan catatan karena pesan kesalahannya menipu. (Ini dengan versi gcc 4.6.3.)

Dan
sumber
23
Saya harus secara eksplisit meletakkan tubuh destructor virtual kosong saya di file definisi (* .cc). Memilikinya di header masih memberi saya kesalahan.
PopcornKing
4
Perhatikan bahwa setelah saya menambahkan destruktor virtual ke file implementasi, maka gcc memberi tahu saya kesalahan yang sebenarnya, yang merupakan badan yang hilang pada fungsi lain.
moodboom
1
@ PodcornKing Saya melihat masalah yang sama. Bahkan mendefinisikan ~Destructor = default;dalam file header tidak membantu. Apakah ada bug yang didokumentasikan yang diajukan terhadap gcc?
RD
ini mungkin masalah yang berbeda, tetapi masalah saya hanya tidak memiliki implementasi untuk destruktor non-virtual (beralih ke pointer unik / bersama dan menghapusnya dari file sumber, tetapi tidak memiliki "implementasi" di header )
svenevs
ini menyelesaikan masalah saya, menambahkan {} body kosong untuk destruktor virtual menghindari kesalahan.
Bogdan Ionitza
56

Jadi, saya sudah menemukan masalah dan itu adalah kombinasi dari logika yang buruk dan tidak sepenuhnya akrab dengan dunia automake / autotools. Saya menambahkan file yang benar ke template Makefile.am saya, tetapi saya tidak yakin langkah mana dalam proses build kami yang benar-benar membuat makefile itu sendiri. Jadi, saya kompilasi dengan makefile lama yang tidak tahu sama sekali tentang file baru saya.

Terima kasih atas tanggapan dan tautan ke FAQ GCC. Saya pasti akan membaca bahwa untuk menghindari masalah ini terjadi karena alasan yang sebenarnya.

RyanG
sumber
43
Singkatnya: .cpp tidak dimasukkan dalam build. Pesan kesalahannya sangat menyesatkan.
Offirmo
67
Untuk pengguna Qt: Anda bisa mendapatkan kesalahan yang sama ini jika Anda lupa untuk meng-moc sebuah header.
Chris Morlier
8
Saya pikir Anda harus menerima jawaban Alexandre Hamez. Orang yang mencari kesalahan ini kemungkinan besar akan membutuhkan solusinya daripada Anda.
Tim
13
-1 Ini mungkin solusi untuk masalah Anda, tetapi ini bukan jawaban untuk pertanyaan awal. Jawaban yang benar adalah bahwa Anda tidak memberikan file objek dengan simbol yang diperlukan. Mengapa Anda gagal menyediakannya adalah cerita lain.
Walter
12
@Walter: Sebenarnya ini adalah jawaban tepat yang saya cari. Yang lain jelas, dan karenanya tidak membantu.
Edgar Bonet
50

Jika Anda menggunakan Qt, coba jalankan ulang qmake. Jika kesalahan ini ada di kelas widget, qmake mungkin gagal memperhatikan bahwa kelas ui vtable harus dibuat ulang. Ini memperbaiki masalah bagi saya.

gluk47
sumber
2
Saya baru saja menghapus seluruh folder dengan membuatnya bekerja juga.
Tomáš Zato - Reinstate Monica
Ini tidak unik qmake, saya memiliki hal yang sama cmake. Bagian dari masalah mungkin adalah bahwa kedua alat memiliki sedikit masalah dengan file header, yang mungkin tidak selalu memicu pembangunan kembali ketika diperlukan.
MSalters
2
Pikir "Rebuild" reran qmake secara otomatis ... ternyata tidak. Saya melakukan "Jalankan qmake" atas saran Anda, lalu "Bangun Kembali" dan itu memperbaiki masalah saya.
yano
45

Referensi yang tidak terdefinisi ke vtable dapat terjadi karena situasi berikut juga. Coba saja ini:

Kelas A Berisi:

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

Kelas B Berisi:

  1. Definisi untuk functionA di atas.
  2. Definisi untuk fungsi di atasB.

Kelas C Berisi: Sekarang Anda sedang menulis Kelas C di mana Anda akan mendapatkannya dari Kelas A.

Sekarang jika Anda mencoba mengkompilasi Anda akan mendapatkan referensi Undefined ke vtable untuk Kelas C sebagai kesalahan.

Alasan:

functionAdidefinisikan sebagai virtual murni dan definisinya disediakan di Kelas B. functionBdidefinisikan sebagai virtual (BUKAN MURNI VIRTUAL) sehingga mencoba untuk menemukan definisi di Kelas A itu sendiri tetapi Anda memberikan definisi di Kelas B.

Larutan:

  1. Jadikan fungsi B sebagai virtual murni (jika Anda memiliki persyaratan seperti itu) virtual void functionB(parameters) =0; (Ini berfungsi, ini Diuji)
  2. Berikan Definisi untuk functionB di Kelas A itu sendiri menjaganya sebagai virtual. (Semoga berhasil karena saya tidak mencoba ini)
Srinivasa Lakkaraju
sumber
@ ilya1725 Suntingan yang Anda sarankan tidak hanya memperbaiki pemformatan dan sejenisnya, Anda juga mengubah jawabannya, misalnya mengatakan bahwa kelas C diturunkan dari B dan bukannya A, dan Anda mengubah solusi kedua. Ini secara substansial mengubah jawabannya. Dalam kasus ini, silakan tinggalkan komentar kepada penulis sebagai gantinya. Terima kasih!
Fabio mengatakan Reinstate Monica
@FabioTurati kelas apa yang mewarisi classC sejak saat itu? Kalimatnya tidak jelas. Juga, apa arti dari "Kelas C Berisi:"?
ilya1725
@ ilya1725 Jawaban ini tidak terlalu jelas, dan saya tidak menentang mengedit dan memperbaikinya. Apa yang saya katakan adalah bahwa hasil edit Anda mengubah makna jawaban, dan itu perubahan yang terlalu drastis. Mudah-mudahan, penulis akan turun tangan dan menjelaskan apa yang dia maksudkan (meskipun dia sudah tidak aktif untuk waktu yang lama).
Fabio mengatakan Reinstate Monica
Terima kasih! Dalam kasus saya, saya hanya memiliki 2 kelas dalam hierarki saya. Kelas A menyatakan metode virtual murni. Deklarasi Kelas B menyatakan bahwa itu akan menggantikan metode ini, tetapi saya belum menulis definisi metode overriden.
Nick Desaulniers
44

Saya hanya mendapatkan kesalahan ini karena file cpp saya tidak ada di makefile.

Akan
sumber
Memang, tampaknya pesan sedikit berubah dari biasanya undefined reference to {function/class/struct}ketika ada virtualhal - hal yang terlibat. Membuang saya.
Keith M
30

Apa itu vtable?

Mungkin berguna untuk mengetahui apa yang dibicarakan pesan kesalahan sebelum mencoba memperbaikinya. Saya akan mulai pada tingkat tinggi, kemudian bekerja ke beberapa detail lebih lanjut. Dengan begitu orang dapat langsung beralih begitu mereka merasa nyaman dengan pemahaman mereka tentang tabel. ... dan ada banyak orang yang melompati sekarang :) Bagi mereka yang tinggal di sekitar:

Sebuah vtable pada dasarnya adalah implementasi paling umum dari polimorfisme di C ++ . Ketika vtables digunakan, setiap kelas polimorfik memiliki vtable di suatu tempat dalam program; Anda dapat menganggapnya sebagai anggota staticdata (tersembunyi) dari kelas. Setiap objek dari kelas polimorfik dikaitkan dengan vtable untuk kelas yang paling diturunkan. Dengan memeriksa asosiasi ini, program dapat mengerjakan keajaiban polimorfiknya. Peringatan penting: vtable adalah detail implementasi. Ini tidak diamanatkan oleh standar C ++, meskipun kebanyakan kompiler C ++ (semua?) Menggunakan vtables untuk mengimplementasikan perilaku polimorfik. Rincian yang saya sajikan adalah pendekatan yang khas atau masuk akal. Kompiler diizinkan untuk menyimpang dari ini!

Setiap objek polimorfik memiliki pointer (tersembunyi) ke vtable untuk kelas objek yang paling diturunkan (mungkin beberapa pointer, dalam kasus yang lebih kompleks). Dengan melihat pointer, program dapat mengetahui jenis objek "asli" (kecuali selama konstruksi, tetapi mari kita lewati case khusus itu). Sebagai contoh, jika suatu objek bertipe Atidak menunjuk ke tabel A, maka objek tersebut sebenarnya merupakan sub-objek dari sesuatu yang berasal A.

Nama "vtable" berasal dari " v irtual function table ". Ini adalah tabel yang menyimpan pointer ke fungsi (virtual). Compiler memilih konvensi untuk bagaimana tabel ditata; pendekatan sederhana adalah melalui fungsi virtual dalam urutan yang dinyatakan dalam definisi kelas. Ketika fungsi virtual dipanggil, program mengikuti pointer objek ke vtable, pergi ke entri yang terkait dengan fungsi yang diinginkan, kemudian menggunakan pointer fungsi tersimpan untuk menjalankan fungsi yang benar. Ada berbagai trik untuk membuat ini berhasil, tetapi saya tidak akan membahasnya di sini.

Di mana / kapan vtabledihasilkan?

Sebuah vtable dihasilkan secara otomatis (kadang-kadang disebut "dipancarkan") oleh kompiler. Kompiler dapat memancarkan vtable di setiap unit terjemahan yang melihat definisi kelas polimorfik, tetapi itu biasanya tidak perlu berlebihan. Alternatif ( digunakan oleh gcc , dan mungkin oleh orang lain) adalah memilih satu unit terjemahan untuk menempatkan vtable, mirip dengan bagaimana Anda akan memilih file sumber tunggal untuk meletakkan anggota data statis kelas. Jika proses pemilihan ini gagal untuk memilih unit terjemahan apa pun, maka vtable menjadi referensi yang tidak ditentukan. Karenanya kesalahan, yang pesannya diakui tidak terlalu jelas.

Demikian pula, jika proses pemilihan memang memilih unit terjemahan, tetapi file objek itu tidak disediakan untuk linker, maka vtable menjadi referensi yang tidak ditentukan. Sayangnya, pesan kesalahan dapat menjadi kurang jelas dalam kasus ini daripada dalam kasus di mana proses seleksi gagal. (Terima kasih kepada para penjawab yang menyebutkan kemungkinan ini. Saya mungkin akan lupa kalau tidak.)

Proses pemilihan yang digunakan oleh gcc masuk akal jika kita mulai dengan tradisi mencurahkan file sumber (tunggal) untuk setiap kelas yang membutuhkan satu untuk implementasinya. Akan lebih baik untuk memancarkan vtable ketika mengkompilasi file sumber itu. Sebut saja itu tujuan kami. Namun, proses seleksi perlu bekerja bahkan jika tradisi ini tidak diikuti. Jadi alih-alih mencari implementasi seluruh kelas, mari kita mencari implementasi anggota kelas tertentu. Jika tradisi diikuti - dan jika anggota itu benar-benar dilaksanakan - maka ini mencapai tujuannya.

Anggota yang dipilih oleh gcc (dan berpotensi oleh kompiler lain) adalah fungsi virtual non-inline pertama yang bukan virtual murni. Jika Anda adalah bagian dari kerumunan yang mendeklarasikan konstruktor dan destruktor sebelum fungsi anggota lainnya, maka destruktor itu memiliki peluang bagus untuk dipilih. (Anda memang ingat membuat destructor virtual, kan?) Ada pengecualian; Saya berharap bahwa pengecualian yang paling umum adalah ketika definisi inline disediakan untuk destruktor dan ketika destruktor default diminta (menggunakan " = default").

Cerdik mungkin memperhatikan bahwa kelas polimorfik diizinkan untuk memberikan definisi sebaris untuk semua fungsi virtualnya. Bukankah itu menyebabkan proses seleksi gagal? Itu dalam kompiler yang lebih tua. Saya telah membaca bahwa kompiler terbaru telah mengatasi situasi ini, tetapi saya tidak tahu nomor versi yang relevan. Saya bisa mencoba mencari ini, tetapi lebih mudah untuk kode di sekitarnya atau menunggu kompiler mengeluh.

Singkatnya, ada tiga penyebab utama kesalahan "referensi tidak ditentukan ke vtable":

  1. Fungsi anggota tidak memiliki definisi.
  2. File objek tidak sedang ditautkan.
  3. Semua fungsi virtual memiliki definisi sebaris.

Penyebab ini sendiri tidak cukup untuk menyebabkan kesalahan sendiri. Sebaliknya, ini adalah apa yang akan Anda atasi untuk menyelesaikan kesalahan. Jangan berharap bahwa dengan sengaja menciptakan salah satu dari situasi ini pasti akan menghasilkan kesalahan ini; ada persyaratan lain. Berharap bahwa menyelesaikan situasi ini akan menyelesaikan kesalahan ini.

(Oke, nomor 3 mungkin sudah cukup ketika pertanyaan ini diajukan.)

Bagaimana cara memperbaiki kesalahan?

Selamat datang kembali orang-orang yang melompat ke depan! :)

  1. Lihatlah definisi kelas Anda. Temukan fungsi virtual non-inline pertama yang bukan virtual murni (bukan " = 0") dan yang definisinya Anda berikan (bukan " = default").
    • Jika tidak ada fungsi seperti itu, coba modifikasi kelas Anda sehingga ada satu. (Kesalahan mungkin teratasi.)
    • Lihat juga jawaban oleh Philip Thomas untuk peringatan.
  2. Temukan definisi untuk fungsi itu. Jika tidak ada, tambahkan! (Kesalahan mungkin teratasi.)
  3. Periksa perintah tautan Anda. Jika tidak menyebutkan file objek dengan definisi fungsi itu, perbaiki itu! (Kesalahan mungkin teratasi.)
  4. Ulangi langkah 2 dan 3 untuk setiap fungsi virtual, lalu untuk setiap fungsi non-virtual, hingga kesalahan teratasi. Jika Anda masih macet, ulangi untuk setiap anggota data statis.

Contoh
Detail tentang apa yang harus dilakukan dapat bervariasi, dan kadang-kadang bercabang menjadi pertanyaan yang terpisah (seperti Apa itu referensi yang tidak ditentukan / kesalahan simbol eksternal yang tidak terselesaikan dan bagaimana cara memperbaikinya? ). Saya akan, bagaimanapun, memberikan contoh apa yang harus dilakukan dalam kasus tertentu yang mungkin membingungkan programmer yang lebih baru.

Langkah 1 menyebutkan memodifikasi kelas Anda sehingga memiliki fungsi tipe tertentu. Jika deskripsi fungsi itu melampaui kepala Anda, Anda mungkin berada dalam situasi yang ingin saya bahas. Ingatlah bahwa ini adalah cara untuk mencapai tujuan; itu bukan satu-satunya cara, dan dengan mudah bisa ada cara yang lebih baik dalam situasi spesifik Anda. Mari kita panggil kelasmu A. Apakah destruktor Anda dinyatakan (dalam definisi kelas Anda) sebagai salah satu

virtual ~A() = default;

atau

virtual ~A() {}

? Jika demikian, dua langkah akan mengubah destruktor Anda menjadi jenis fungsi yang kita inginkan. Pertama, ubah baris itu menjadi

virtual ~A();

Kedua, letakkan baris berikut dalam file sumber yang merupakan bagian dari proyek Anda (sebaiknya file dengan implementasi kelas, jika Anda memilikinya):

A::~A() {}

Itu membuat destruktor (virtual) Anda non-inline dan tidak dihasilkan oleh kompiler. (Jangan ragu untuk memodifikasi hal-hal agar lebih cocok dengan gaya pemformatan kode Anda, seperti menambahkan komentar header ke definisi fungsi.)

JaMiT
sumber
Oh, bravo! Untuk penjelasan yang sangat terperinci dan sangat baik.
David C. Rankin
Harus gulir ke bawah jalan jauh untuk membaca ini. Dapatkan upvote untuk penjelasan yang luar biasa!
Thomas
24

Ada banyak spekulasi yang terjadi di berbagai jawaban di sini. Di bawah ini saya akan memberikan kode yang cukup minimal yang mereproduksi kesalahan ini dan menjelaskan mengapa itu terjadi.

Kode Minimal Cukup untuk Mereproduksi Kesalahan Ini

IBase.hpp

#pragma once

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

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

Anda dapat mengkompilasi ini menggunakan GCC seperti ini:

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

Anda sekarang dapat mereproduksi kesalahan dengan menghapus = 0di IBase.hpp. Saya mendapatkan kesalahan ini:

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

Penjelasan

Perhatikan bahwa kode di atas tidak memerlukan destruktor virtual, konstruktor atau file tambahan lainnya agar kompilasi berhasil (walaupun Anda harus memilikinya).

Cara untuk memahami kesalahan ini adalah sebagai berikut: Linker mencari konstruktor dari IBase. Ini akan membutuhkannya untuk konstruktor Derived. Namun karena Derived mengganti metode dari IBase, ia memiliki vtable yang melekat padanya yang akan merujuk IBase. Ketika tautan mengatakan "referensi yang tidak terdefinisi ke vtable untuk IBase" pada dasarnya berarti Derived memiliki referensi vtable ke IBase tetapi tidak dapat menemukan kode objek IBase yang dikompilasi untuk dicari. Jadi intinya adalah bahwa kelas IBase memiliki deklarasi tanpa implementasi. Ini berarti metode dalam IBase dinyatakan sebagai virtual tetapi kami lupa menandainya sebagai virtual murni ATAU memberikan definisi.

Tip Perpisahan

Jika semuanya gagal maka salah satu cara untuk men-debug kesalahan ini adalah dengan membangun program minimal yang melakukan kompilasi dan kemudian terus mengubahnya sehingga sampai ke keadaan yang Anda inginkan. Di antaranya, terus kompilasi untuk melihat kapan mulai gagal.

Catatan tentang ROS dan sistem build Catkin

Jika Anda mengkompilasi set kelas di atas di ROS menggunakan sistem build catkin maka Anda perlu mengikuti baris di CMakeLists.txt:

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

Baris pertama pada dasarnya mengatakan bahwa kita ingin membuat executable bernama myclass dan kode untuk membangunnya dapat ditemukan file-file berikut. Salah satu file ini harus memiliki main (). Perhatikan bahwa Anda tidak harus menentukan file .hpp di mana saja di CMakeLists.txt. Anda juga tidak perlu menentukan Derived.cpp sebagai pustaka.

Shital Shah
sumber
18

Saya baru saja menemukan penyebab lain untuk kesalahan ini yang dapat Anda periksa.

Kelas dasar mendefinisikan a fungsi virtual murni sebagai:

virtual int foo(int x = 0);

Dan subclass punya

int foo(int x) override;

Masalahnya adalah kesalahan ketik itu "=0" seharusnya berada di luar kurung:

virtual int foo(int x) = 0;

Jadi, jika Anda menggulir sejauh ini ke bawah, Anda mungkin tidak menemukan jawabannya - ini adalah hal lain yang perlu diperiksa.

Philip Thomas
sumber
12

Ini bisa terjadi dengan mudah jika Anda lupa menautkan ke file objek yang memiliki definisi.

Hazok
sumber
1
Harap tambahkan beberapa deskripsi lagi untuk jawaban Anda dan kemungkinan perbaikan.
Mohit Jain
1
Lupa taut dapat mencakup lupa menambahkan untuk membangun instruksi. Dalam kasus saya, file cpp saya memiliki segalanya yang 'jelas' kecuali saya lupa menambahkan file cpp ke daftar sumber (dalam CMakeLists.txt saya, tetapi hal yang sama dapat terjadi pada sistem build lain seperti dalam file .pro). Akibatnya, semuanya dikompilasi dan kemudian saya mendapatkan kesalahan pada waktu tautan ...
bijak
@Mohit Jain Cara menautkan file objek tergantung pada pengaturan dan tooling lingkungan. Sayangnya perbaikan khusus untuk satu orang mungkin berbeda untuk orang lain (mis. CMake vs alat berpemilik vs IDE vs dll.)
Hazok
11

GNU C ++ compiler harus membuat keputusan di mana harus meletakkan vtablejika Anda memiliki definisi fungsi virtual dari suatu objek yang tersebar di beberapa unit kompilasi (misalnya beberapa objek definisi fungsi virtual dalam file .cpp yang lain di file lain. file cpp, dan sebagainya).

Compiler memilih untuk meletakkan vtabledi tempat yang sama dengan di mana fungsi virtual pertama yang dideklarasikan didefinisikan.

Sekarang jika Anda karena suatu alasan lupa untuk memberikan definisi untuk fungsi virtual pertama yang dideklarasikan dalam objek (atau keliru lupa untuk menambahkan objek yang dikompilasi pada tahap menghubungkan), Anda akan mendapatkan kesalahan ini.

Sebagai efek samping, harap dicatat bahwa hanya untuk fungsi virtual khusus ini Anda tidak akan mendapatkan kesalahan penghubung tradisional seperti Anda kehilangan fungsi foo .

Iulian Popa
sumber
8

Bukan untuk menyeberang pos tetapi. Jika Anda berurusan dengan warisan , google hit kedua adalah apa yang saya lewatkan, yaitu. semua metode virtual harus didefinisikan.

Seperti:

virtual void fooBar() = 0;

Lihat answare C ++ Undefined Reference ke vtable dan inheritance untuk detailnya. Baru sadar itu sudah disebutkan di atas, tapi sih mungkin bisa membantu seseorang.

Victor Häggqvist
sumber
8

Oke, solusi untuk ini adalah Anda mungkin telah kehilangan definisi. Lihat contoh di bawah ini untuk menghindari kesalahan kompilasi 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
sumber
7
  • Apakah Anda yakin CDasherComponentmemiliki badan penghancur? Jelas tidak di sini - pertanyaannya adalah apakah itu ada dalam file .cc.
  • Dari perspektif gaya, CDasherModuleharus secara eksplisit mendefinisikan destruktornya virtual.
  • Sepertinya CGameModulememiliki tambahan } di akhir (setelah }; // for the class).
  • Apakah CGameModuleterkait dengan perpustakaan yang mendefinisikan CDasherModuledan CDasherComponent?
Stephen
sumber
- Ya, CDasherComponent memiliki badan destruktor di cpp. Saya pikir itu dideklarasikan di .h ketika saya memposting ini. - Sepatutnya dicatat. - Itu adalah braket tambahan yang saya tambahkan secara tidak sengaja ketika membuka dokumentasi. - Sejauh yang saya mengerti, ya. Saya telah memodifikasi file automake yang tidak saya tulis, tetapi saya telah mengikuti pola yang telah bekerja untuk kelas lain dengan pola pewarisan yang sama dari kelas yang sama, jadi kecuali saya membuat kesalahan bodoh (Sepenuhnya mungkin) Saya tidak berpikir begitu.
RyanG
@RyanG: coba pindahkan semua definisi fungsi virtual ke definisi kelas. Pastikan semuanya ada di sana dan lihat apakah hasilnya berubah.
Stephen
5

Mungkin kehilangan destruktor virtual adalah faktor penyebabnya?

virtual ~CDasherModule(){};
DevByStarlight
sumber
5

Ini adalah hasil pencarian pertama bagi saya jadi saya pikir saya akan menambahkan hal lain untuk memeriksa: pastikan definisi fungsi virtual benar-benar ada di kelas. Dalam kasus saya, saya memiliki ini:

File tajuk:

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

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

dan dalam file .cc saya:

void foo() {
  ...
}

Ini harus dibaca

void B::foo() {
}
RedSpikeyThing
sumber
3

Tidak mungkin. Jelas ~CDasherModule() {}tidak ada.

Bretzelus
sumber
3

Begitu banyak jawaban di sini, tetapi tak satu pun dari mereka yang membahas apa masalah saya. Saya memiliki yang berikut ini:


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

Dan di file lain (termasuk dalam kompilasi dan menautkan, tentu saja)

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

Yah ini tidak berhasil dan saya mendapat kesalahan yang dibicarakan semua orang. Untuk mengatasinya, saya harus memindahkan definisi Foo yang sebenarnya dari deklarasi kelas sebagai berikut:

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

C::Foo(){
   //bar
}

Saya bukan guru C ++ jadi saya tidak bisa menjelaskan mengapa ini lebih benar tetapi itu memecahkan masalah bagi saya.

Nathan Daniels
sumber
Saya bukan guru C ++, tetapi tampaknya terkait dengan mencampur deklarasi dan definisi dalam file yang sama, dengan file definisi tambahan.
Terry G Lorber
1
Ketika definisi fungsi berada di dalam definisi kelas Anda, itu secara implisit dinyatakan "inline". Pada saat itu, semua fungsi virtual Anda di-inline. Saat Anda memindahkan fungsi di luar definisi kelas, itu bukan lagi fungsi "inline". Karena Anda memiliki fungsi virtual non-inline, kompiler Anda tahu di mana harus memancarkan vtable. (Penjelasan yang lebih menyeluruh tidak akan cocok dengan komentar.)
JaMiT
3

Jadi saya menggunakan Qt dengan Windows XP dan kompiler MinGW dan hal ini membuat saya gila.

Pada dasarnya moc_xxx.cpp dibuat kosong bahkan ketika saya ditambahkan

Q_OBJECT

Menghapus semua yang membuat fungsi virtual, eksplisit dan apa pun yang Anda duga tidak berfungsi. Akhirnya saya mulai menghapus baris demi baris dan ternyata sudah

#ifdef something

Di sekitar file. Bahkan ketika #ifdef benar file moc tidak dihasilkan.

Jadi menghapus semua # jika masalah diperbaiki.

Hal ini tidak terjadi pada Windows dan VS 2013.

Daniel Georgiev
sumber
Mengomentari baris Q_OBJECT membuat aplikasi pengujian sederhana saya dibuat dengan sederhana g++ *.cpp .... (Membutuhkan sesuatu yang cepat dan kotor tetapi qmake penuh dengan kesedihan.)
Nathan Kidd
2

Jika semuanya gagal, cari duplikasi. Saya salah arah oleh referensi awal eksplisit untuk konstruktor dan destruktor sampai saya membaca referensi di posting lain. Ini pun metode yang belum terselesaikan. Dalam kasus saya, saya pikir saya telah mengganti deklarasi yang menggunakan char * xml sebagai parameter dengan yang menggunakan const char * xml yang tidak perlu, tetapi sebagai gantinya, saya telah membuat yang baru dan meninggalkan yang lain di tempatnya.

Jerry Miller
sumber
2

Ada banyak kemungkinan yang disebutkan untuk menyebabkan kesalahan ini, dan saya yakin banyak dari mereka yang menyebabkan kesalahan. Dalam kasus saya, ada satu lagi definisi dari kelas yang sama, karena duplikasi file sumber. File ini dikompilasi, tetapi tidak ditautkan, sehingga linker mengeluh karena tidak dapat menemukannya.

Untuk meringkas, saya akan mengatakan bahwa jika Anda telah menatap kelas cukup lama dan tidak dapat melihat apa masalah yang mungkin menyebabkannya, cari masalah build seperti file yang hilang atau file yang digandakan.

crw4096
sumber
2

Dalam kasus saya, saya menggunakan Qt dan telah mendefinisikan QObjectsubclass dalam file foo.cpp(bukan .h). Cara mengatasinya adalah menambahkan #include "foo.moc"di akhir foo.cpp.

Stefan Monov
sumber
2

Saya pikir itu juga layak disebutkan bahwa Anda juga akan mendapatkan pesan ketika Anda mencoba untuk menautkan ke objek kelas mana pun yang memiliki setidaknya satu metode virtual dan tautan tidak dapat menemukan file. Sebagai contoh:

Foo.hpp:

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

Foo.cpp:

#include "Foo.hpp"

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

Dikompilasi dengan:

g++ Foo.cpp -c

Dan main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Dikompilasi dan dihubungkan dengan:

g++ main.cpp -o main

Memberikan kesalahan favorit kami:

/tmp/cclKnW0g.o: Dalam fungsi main': main.cpp:(.text+0x1a): undefined reference tovtable untuk Foo 'collect2: error: ld mengembalikan 1 status keluar

Ini terjadi karena pemahaman saya karena:

  1. Vtable dibuat per kelas pada waktu kompilasi

  2. Linker tidak memiliki akses ke vtable yang ada di Foo.o

Adam Stepniak
sumber
1

Saya mendapatkan kesalahan ini dalam skenario berikut

Pertimbangkan kasus di mana Anda telah mendefinisikan implementasi fungsi anggota suatu kelas dalam file header itu sendiri. File header ini adalah header yang diekspor (dengan kata lain, mungkin disalin ke beberapa yang umum / termasuk langsung dalam basis kode Anda). Sekarang Anda telah memutuskan untuk memisahkan implementasi fungsi anggota ke file .cpp. Setelah Anda memisahkan / memindahkan implementasinya ke .cpp, file header sekarang hanya memiliki prototipe fungsi anggota di dalam kelas. Setelah perubahan di atas, jika Anda membangun basis kode Anda, Anda mungkin mendapatkan kesalahan "referensi tidak terdefinisi untuk 'vtable ...".

Untuk memperbaiki ini, sebelum membangun, pastikan Anda menghapus file header (yang Anda buat perubahan) di direktori umum / sertakan. Juga pastikan Anda mengubah makefile Anda untuk mengakomodasi / menambahkan file .o baru yang dibangun dari file .cpp baru yang baru saja Anda buat. Ketika Anda melakukan langkah-langkah ini kompiler / tautan tidak akan lagi mengeluh.

Sidharth Middela
sumber
aneh. Jika header akan disalin di tempat lain, sistem build harus memperbarui salinan secara otomatis segera setelah aslinya diubah, dan sebelum dimasukkan ke dalam file lain. Jika Anda harus melakukannya secara manual, Anda kacau.
Offirmo
1

Saya mendapatkan jenis kesalahan ini dalam situasi di mana saya mencoba menautkan ke suatu objek ketika saya membuat bug yang mencegah objek ditambahkan ke arsip.

Katakanlah saya punya libXYZ.a yang seharusnya memiliki bioseq.o di int tetapi tidak.

Saya mendapat kesalahan:

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

Ini berbeda dari semua hal di atas. Saya akan menyebut objek yang hilang ini dalam masalah arsip.

Kemin Zhou
sumber
0

Mungkin juga Anda mendapatkan pesan seperti

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'

jika Anda lupa mendefinisikan fungsi virtual kelas FakeClass1 saat Anda mencoba menautkan tes unit untuk SomeClass kelas lain.

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

Dan

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

Dalam hal ini saya sarankan Anda memeriksa palsu Anda untuk class1 sekali lagi. Anda mungkin menemukan bahwa Anda mungkin lupa mendefinisikan fungsi virtual ForgottenFuncdi kelas palsu Anda.

Yuriy
sumber
0

Saya mendapatkan kesalahan ini ketika saya menambahkan kelas kedua ke pasangan sumber / header yang ada. Dua header kelas dalam file .h yang sama, dan definisi fungsi untuk dua kelas dalam file .cpp yang sama.

Saya telah melakukan ini dengan sukses sebelumnya, dengan kelas-kelas yang dimaksudkan untuk bekerja sama secara erat, tetapi tampaknya sesuatu tidak menyukai saya saat ini. Masih tidak tahu apa, tetapi membaginya menjadi satu kelas per unit kompilasi memperbaikinya.


Upaya yang gagal:

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

Sekali lagi, menambahkan pasangan sumber / tajuk baru dan memotong / menempel kelas IconWithData kata demi kata di sana "hanya bekerja".

AaronD
sumber
0

Kasus saya adalah satu konyol, saya memiliki tambahan "setelah #includeoleh kesalahan dan coba tebak?

undefined reference to vtable!

Saya telah menggaruk-garuk kepala dan wajah selama berjam-jam berkomentar fungsi virtual untuk melihat apakah ada yang berubah, dan akhirnya dengan menghapus ekstra " , semuanya sudah diperbaiki! Hal-hal semacam ini benar-benar perlu menghasilkan kesalahan kompilasi bukan kesalahan tautan.

Secara ekstra ", maksud saya:

#include "SomeHeader.h""
Bahram Pouryousefi
sumber
0

Dalam kasus saya, saya memiliki kelas dasar bernama Person dan dua kelas turunan bernama Mahasiswa dan Profesor.

Bagaimana program saya diperbaiki adalah, 1. Saya membuat semua fungsi di kelas dasar Pure Virtual. 2. Saya menggunakan semua destruktor virtual sebagaidefault ones.

Gaurav Kumar
sumber
-3

Saya mendapatkan kesalahan ini hanya karena nama argumen konstruktor berbeda dalam file header dan file implementasi. Tanda tangan konstruktor adalah

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

dan apa yang saya tulis dalam implementasi dimulai dengan

PointSet (const PointSet & pest, Parent * parent)

jadi saya tidak sengaja mengganti "pset" dengan "hama". Kompilator mengeluh tentang yang satu ini dan dua konstruktor lain di mana tidak ada kesalahan sama sekali. Saya menggunakan g ++ versi 4.9.1 di bawah Ubuntu. Dan mendefinisikan destruktor virtual di kelas turunan ini tidak membuat perbedaan (didefinisikan di kelas dasar). Saya tidak akan pernah menemukan bug ini jika saya tidak menempelkan badan konstruktor di file header, sehingga mendefinisikan mereka di kelas.

vitke
sumber
7
Itu tidak ada bedanya sama sekali, Anda pasti memiliki kesalahan di tempat lain dan memperbaikinya secara tidak sengaja.
MM