簡體   English   中英

對 vtable 的未定義引用

[英]Undefined reference to vtable

在構建我的 C++ 程序時,我收到錯誤消息

未定義的對 'vtable... 的引用

這個問題的原因是什么? 我如何解決它?


碰巧我收到了以下代碼的錯誤(有問題的 class 是 CGameModule。)我終生無法理解問題所在。 一開始還以為跟忘記給一個虛擬function一個body有關,但據我了解,一切都在這里。 inheritance 鏈有點長,但這里是相關的源代碼。 我不確定我應該提供哪些其他信息。

注意:構造函數似乎是發生此錯誤的地方。

我的代碼:

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

GCC 常見問題有一個條目:

解決方案是確保定義所有非純的虛擬方法。 請注意,即使聲明為純虛擬 [class.dtor]/7,也必須定義析構函數。

因此,您需要為虛擬析構函數提供一個定義:

virtual ~CDasherModule()
{ }

值得一提的是,忘記虛擬析構函數上的主體會生成以下內容:

未定義對 CYourClass 的 vtable 的引用。

我添加注釋是因為錯誤消息具有欺騙性。 (這是使用 gcc 版本 4.6.3。)

TL;DR - 解釋為什么 vtable 可能會丟失以及如何修復它。 答案很長,因為它解釋了為什么編譯器可能會忘記創建 vtable。 (編輯)

什么是vtable

在嘗試修復它之前了解錯誤消息所談論的內容可能會很有用。 我將從高層次開始,然后深入研究更多細節。 這樣,一旦人們對 vtable 的理解感到滿意,他們就可以跳過。 ......現在有很多人跳過。 :) 對於那些堅持的人:

vtable 基本上是C++ 中最常見的多態實現。 使用 vtable 時,每個多態類在程序中的某處都有一個 vtable; 您可以將其視為類的(隱藏的) static數據成員。 多態類的每個對象都與其最派生類的 vtable 相關聯。 通過檢查這種關聯,程序可以發揮它的多態魔法。 重要警告: vtable 是一個實現細節。 盡管大多數(全部?)C++ 編譯器使用 vtable 來實現多態行為,但 C++ 標准並未強制要求這樣做。 我提出的細節要么是典型的,要么是合理的。 允許編譯器偏離這一點!

每個多態對象都有一個(隱藏的)指向該對象派生最多的類的 vtable 指針(在更復雜的情況下可能是多個指針)。 通過查看指針,程序可以判斷對象的“真實”類型是什么(構造期間除外,但讓我們跳過那個特殊情況)。 例如,如果類型的對象A不指向的虛函數表A ,然后將該對象實際上是衍生自一些子對象A

“虛函數表”這個名字來源於“V irtual功能”。 它是一個存儲指向(虛擬)函數的指針的表。 編譯器選擇它的表格布局約定; 一個簡單的方法是按照它們在類定義中聲明的順序遍歷虛函數。 當一個虛函數被調用時,程序跟隨對象的指針指向一個虛表,轉到與所需函數關聯的入口,然后使用存儲的函數指針調用正確的函數。 有各種各樣的技巧來完成這項工作,但我不會在這里討論這些。

vtable在哪里/何時生成?

編譯器會自動生成(有時稱為“發出”)一個 vtable。 編譯器可以在看到多態類定義的每個翻譯單元中發出一個 vtable,但這通常是不必要的矯枉過正。 另一種方法(由 gcc 使用,也可能由其他人使用)是選擇一個單獨的翻譯單元來放置 vtable,類似於選擇一個源文件來放置類的靜態數據成員的方式。 如果此選擇過程未能選擇任何翻譯單元,則 vtable 將成為未定義的引用。 因此出現了錯誤,其信息無疑不是特別清楚。

類似地,如果選擇過程確實選擇了一個翻譯單元,但該目標文件沒有提供給鏈接器,則 vtable 成為未定義的引用。 不幸的是,與選擇過程失敗的情況相比,這種情況下的錯誤消息甚至可能更不清楚。 (感謝提到這種可能性的回答者。否則我可能會忘記它。)

如果我們從將一個(單個)源文件專門用於每個需要一個源文件來實現的類的傳統開始,那么 gcc 使用的選擇過程是有意義的。 在編譯該源文件時發出 vtable 會很好。 讓我們稱之為我們的目標。 然而,即使不遵循這一傳統,選擇過程也需要進行。 因此,與其查找整個類的實現,不如查找類的特定成員的實現。 如果遵循傳統——如果該成員實際上得到了實施——那么這就實現了目標。

gcc(也可能是其他編譯器)選擇的成員是第一個不是純虛的非內聯虛函數。 如果您是在其他成員函數之前聲明構造函數和析構函數的人群中的一員,那么該析構函數很有可能被選中。 (您確實記得將析構函數設為虛擬,對嗎?)也有例外; 我希望最常見的例外是為析構函數提供內聯定義以及請求默認析構函數時(使用“ = default ”)。

精明的人可能會注意到,允許多態類為其所有虛函數提供內聯定義。 這不會導致選擇過程失敗嗎? 它在較舊的編譯器中。 我讀過最新的編譯器已經解決了這種情況,但我不知道相關的版本號。 我可以嘗試查找它,但是圍繞它編寫代碼或等待編譯器抱怨更容易。

綜上所述,“undefined reference to vtable”錯誤主要有以下三個原因:

  1. 成員函數缺少其定義。
  2. 未鏈接目標文件。
  3. 所有虛函數都有內聯定義。

這些原因本身不足以導致錯誤。 相反,這些是您要解決的錯誤。 不要指望故意制造這些情況之一肯定會產生此錯誤; 還有其他要求。 請期待解決這些情況將解決此錯誤。

(好吧,當問到這個問題時,數字 3 可能就足夠了。)

如何修復錯誤?

歡迎回來的人跳過! :)

  1. 看看你的類定義。 找到第一個非純虛函數(不是“ = 0 ”)並且您提供其定義(不是“ = default ”)的第一個非內聯虛函數。
    • 如果沒有這樣的功能,請嘗試修改您的類,以便有一個。 (錯誤可能已解決。)
    • 另請參閱Philip Thomas 的回答以獲取警告。
  2. 找到該函數的定義。 如果缺少,請添加它! (錯誤可能已解決。)
    • 如果函數定義在類定義之外,則確保函數定義使用限定名稱,如ClassName::function_name
  3. 檢查您的鏈接命令。 如果它沒有提到具有該函數定義的目標文件,請修復它! (錯誤可能已解決。)
  4. 對每個虛函數重復步驟 2 和 3,然后對每個非虛函數重復步驟 2 和 3,直到錯誤得到解決。 如果您仍然卡住,請對每個靜態數據成員重復此操作。

例子
要做什么的細節可能會有所不同,有時會分為單獨的問題(例如什么是未定義的引用/未解決的外部符號錯誤以及如何修復它? )。 不過,我將提供一個示例,說明在可能會迷惑新程序員的特定情況下該怎么做。

第 1 步提到修改您的類,使其具有某種類型的功能。 如果該功能的描述超出了您的腦海,您可能處於我打算解決的情況。 請記住,這是實現目標的一種方式; 這不是唯一的方法,在您的特定情況下很容易有更好的方法。 讓我們稱您為A類。 您的析構函數是否(在您的類定義中)聲明為

virtual ~A() = default;

或者

virtual ~A() {}

? 如果是這樣,兩個步驟會將您的析構函數更改為我們想要的函數類型。 首先,將該行更改為

virtual ~A();

其次,將以下行放在作為項目一部分的源文件中(最好是具有類實現的文件,如果有的話):

A::~A() {}

這使您的(虛擬)析構函數是非內聯的,而不是由編譯器生成的。 (隨意修改內容以更好地匹配您的代碼格式樣式,例如向函數定義添加標題注釋。)

所以,我已經解決了這個問題,這是錯誤的邏輯和對 automake/autotools 世界不完全熟悉的組合。 我正在將正確的文件添加到我的 Makefile.am 模板中,但我不確定我們構建過程中的哪一步實際創建了 makefile 本身。 因此,我正在使用一個舊的 makefile 進行編譯,該文件對我的新文件一無所知。

感謝您的回復和 GCC 常見問題解答的鏈接。 我一定會閱讀它,以避免由於真正的原因而發生此問題。

如果您使用的是 Qt,請嘗試重新運行 qmake。 如果這個錯誤出現在小部件的類中,qmake 可能沒有注意到應該重新生成 ui 類 vtable。 這為我解決了這個問題。

由於以下情況也可能發生對 vtable 的未定義引用。 試試這個:

A類包含:

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

B類包含:

  1. 上述函數的定義A.
  2. 上述函數的定義 B.

C 類包含:現在您正在編寫一個 C 類,您將在其中從 A 類派生它。

現在,如果您嘗試編譯,您將收到 Undefined reference to vtable for Class C 作為錯誤。

原因:

functionA被定義為純虛擬,其定義在 B 類中提供。 functionB被定義為虛擬(非純虛擬),因此它試圖在 A 類本身中找到它的定義,但您在 B 類中提供了它的定義。

解決方案:

  1. 使函數 B 為純虛擬(如果您有這樣的要求) virtual void functionB(parameters) =0; (這是經過測試的)
  2. 為 Class A 中的 functionB 提供定義,將其保持為 virtual 。 (希望它有效,因為我沒有嘗試過)

我只是收到這個錯誤,因為我的 .cpp 文件不在 makefile 中。

通常,如果您忘記編譯或鏈接到包含定義的特定目標文件,您將遇到此錯誤。

這里的各種答案都有很多猜測。 我將在下面給出一個相當少的代碼來重現這個錯誤並解釋它發生的原因。

重現此錯誤的相當少的代碼

數據庫文件

#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() {}

我的類.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

您現在可以通過刪除 IBase.hpp 中的= 0來重現該錯誤。 我收到此錯誤:

~/.../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 的構造函數。 但是,由於 Derived 覆蓋了來自 IBase 的方法,因此它附加了將引用 IBase 的 vtable。 當鏈接器說“未定義對 IBase 的 vtable 的引用”時,它基本上意味着 Derived 具有對 IBase 的 vtable 引用,但它找不到任何 IBase 的編譯目標代碼來查找。 所以底線是類 IBase 有沒有實現的聲明。 這意味着 IBase 中的方法被聲明為虛擬,但我們忘記將其標記為純虛擬或提供其定義。

分離提示

如果所有其他方法都失敗了,那么調試此錯誤的一種方法是構建可以編譯的最小程序,然后不斷更改它,使其達到您想要的狀態。 在兩者之間,繼續編譯以查看何時開始失敗。

關於 ROS 和 Catkin 構建系統的說明

如果您使用 catkin 構建系統在 ROS 中編譯上述一組類,那么您將需要在 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()。 請注意,您不必在 CMakeLists.txt 中的任何位置指定 .hpp 文件。 此外,您不必將 Derived.cpp 指定為庫。

我剛剛遇到了您可以檢查的此錯誤的另一個原因。

基類定義了一個純虛函數

virtual int foo(int x = 0);

子類有

int foo(int x) override;

問題是"=0"應該在括號之外的拼寫錯誤:

virtual int foo(int x) = 0;

所以,如果你向下滾動這么遠,你可能沒有找到答案 - 這是其他需要檢查的東西。

GNU C++ 編譯器必須決定將vtable放在哪里,以防您將對象的虛函數定義分布在多個編譯單元中(例如,某些對象虛函數定義在 .cpp 文件中,其他在另一個文件中) .cpp 文件,等等)。

編譯器選擇將vtable放在與定義第一個聲明的虛函數相同的位置。

現在,如果您由於某種原因忘記為對象中聲明的第一個虛函數提供定義(或錯誤地忘記在鏈接階段添加已編譯的對象),您將收到此錯誤。

作為副作用,請注意只有對於這個特定的虛擬函數,您不會像缺少函數 foo那樣出現傳統的鏈接器錯誤。

如果您忘記鏈接到具有定義的目標文件,則這很容易發生。

  • 你確定CDasherComponent有一個析構函數體嗎? 它絕對不在這里 - 問題是它是否在 .cc 文件中。
  • 從樣式的角度來看, CDasherModule應該明確定義其析構函數virtual
  • 看起來CGameModule在末尾有一個額外的} (在}; // for the class之后)。
  • CGameModule是否與定義CDasherModuleCDasherComponent的庫相關聯?

不是交叉帖子而是。 如果您正在處理繼承,第二個谷歌命中是我錯過的,即。 應該定義所有虛擬方法。

如:

virtual void fooBar() = 0;

有關詳細信息,請參閱 answare C++ Undefined Reference to vtable 和繼承 剛剛意識到上面已經提到過,但它可能對某人有所幫助。

好的,解決方案是您可能錯過了定義。 請參見下面的示例,以避免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
{

}

也許缺少虛擬析構函數是促成因素?

virtual ~CDasherModule(){};

這是我的第一個搜索結果,所以我想我還要檢查另一件事:確保虛擬函數的定義確實在類中。 就我而言,我有這個:

頭文件:

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

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

在我的.cc文件中:

void foo() {
  ...
}

這應該讀

void B::foo() {
}

可能不是。 肯定~CDasherModule() {}丟失了。

這里有這么多答案,但似乎沒有一個涵蓋我的問題。 我有以下內容:


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

並且在另一個文件中(當然包括在編譯和鏈接中)

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

好吧,這沒有用,我得到了每個人都在談論的錯誤。 為了解決這個問題,我不得不將Foo的實際定義移出類聲明,例如:

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

C::Foo(){
   //bar
}

我不是C ++專家,所以我無法解釋為什么這更正確,但卻為我解決了問題。

所以我在 Windows XP 和 MinGW 編譯器中使用 Qt,這件事讓我發瘋。

基本上,即使我被添加,moc_xxx.cpp 也生成為空

Q_OBJECT

刪除所有使函數變得虛擬、顯式和任何您猜想都不起作用的東西。 最后我開始逐行刪除,結果我有

#ifdef something

圍繞文件。 即使#ifdef 為真,也不會生成moc 文件。

所以刪除所有#ifdefs 解決了這個問題。

Windows 和 VS 2013 沒有發生這種情況。

在我的情況下,我使用 Qt 並在foo.cpp (不是.h )文件中定義了一個QObject子類。 此修復程序是添加#include "foo.moc"在結束foo.cpp

對於使用 CMakeList.txt 的 Qt 用戶

在您的 CMakeLists.txt 中添加這一行: set(CMAKE_AUTOMOC ON)

正如克里斯·莫勒 (Chris Morler) 所解釋的,如果您忘記對標頭進行模擬,則會收到此錯誤

如果所有其他方法都失敗了,請尋找重復項。 我被對構造函數和析構函數的顯式初始引用誤導了,直到我在另一篇文章中閱讀了引用。 這是任何未解決的方法。 就我而言,我以為我已經將使用char *xml作為參數的聲明替換為使用不必要的麻煩const char *xml ,但相反,我創建了一個新的聲明並將另一個保留在原位。

提到了很多導致此錯誤的可能性,我敢肯定其中有許多確實會導致該錯誤。 在我的情況下,由於源文件的重復,因此存在相同類的另一個定義。 該文件已編譯,但未鏈接,因此鏈接器抱怨無法找到它。

總而言之,我想說的是,如果您盯着該類學習了足夠長的時間,而又看不到可能引起語法錯誤的原因,請查找諸如丟失文件或重復文件之類的構建問題。

我認為還值得一提的是,當您嘗試鏈接到任何具有至少一個虛擬方法的類的對象並且鏈接器找不到該文件時,您也會收到該消息。 例如:

foo.hpp:

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

文件.cpp:

#include "Foo.hpp"

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

編譯:

g++ Foo.cpp -c

和 main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

編譯並鏈接到:

g++ main.cpp -o main

給出我們最喜歡的錯誤:

/tmp/cclKnW0g.o: 在函數main': main.cpp:(.text+0x1a): undefined reference to vtable for Foo' collect2: 錯誤: ld 返回 1 退出狀態

這是由於我的不理解而發生的,因為:

  1. Vtable 是在編譯時為每個類創建的

  2. 鏈接器無權訪問 Foo.o 中的 vtable

在以下情況下出現此錯誤

考慮一種情況,您已經在頭文件本身中定義了類的成員函數的實現。 該頭文件是導出的頭(換句話說,可以將其直接復制到代碼庫中的一些common / include中)。 現在,您已經決定將成員函數的實現分離到.cpp文件中。 在將實現分離/移動到.cpp之后,頭文件現在僅具有類內部成員函數的原型。 完成上述更改后,如果您構建代碼庫,則可能會收到“未定義對'vtable ...的引用”錯誤。

要解決此問題,請在構建之前確保刪除common / include目錄中的頭文件(對其進行了更改)。 另外,還要確保將makefile更改為適應/添加從剛創建的新.cpp文件構建的新.o文件。 當您執行這些步驟時,編譯器/鏈接器將不再抱怨。

當我遇到一個make bug導致無法將對象添加到存檔中時,在嘗試鏈接到對象的情況下出現了此類錯誤。

說我有libXYZ.a,它應該在int中有bioseq.o,但沒有。

我收到一個錯誤:

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

這與以上所有都不相同。 我會在存檔問題中稱這個丟失的對象。

這是 GCC 中的錯誤功能。 也就是說,G++ 編譯器本身不能抱怨未定義的虛擬方法,因為它們可以在其他地方定義。 但是 - 它不存儲有關缺少哪些虛擬成員的信息; 它只存儲一個未定義的 vtable 符號,然后 linker 抱怨。

相反,如果要列出缺失的成員,linker 可能會告訴你他們是什么。

針對 GCC 存在一個關於此問題的開放錯誤:錯誤 42540 不幸的是,它已經 13 歲了:-(

您也可能會收到類似

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'

如果您在嘗試鏈接另一個類SomeClass的單​​元測試時忘記定義FakeClass1類的虛函數。

//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在假類中定義虛擬函數ForgottenFunc

當我在現有的源/標題對中添加第二個類時,出現此錯誤。 同一.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類剪切/粘貼到“剛剛工作”。

在我重新排列了makefiles列表之前,對我沒有任何幫助。

我嘗試了JaMIT 的所有詳細步驟,仍然被這個錯誤難了。 經過大量的頭部撞擊后,我想通了。 我粗心了 您應該能夠使用以下示例代碼重現這個令人痛苦的錯誤。

[jaswantp@jaswant-arch build]$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++,d --with-isl --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-install-libiberty --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-libunwind-exceptions --disable-werror gdc_include_dir=/usr/include/dlang/gdc
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.2.0 (GCC) 

// CelesetialBody.h
class CelestialBody{
public:
    virtual void Print();
protected:
    CelestialBody();
    virtual ~CelestialBody();
};
// CelestialBody.cpp
#include "CelestialBody.h"

CelestialBody::CelestialBody() {}

CelestialBody::~CelestialBody() = default;

void CelestialBody::Print() {}
// Planet.h
#include "CelestialBody.h"

class Planet : public CelestialBody
{
public:
    void Print() override;
protected:
    Planet();
    ~Planet() override;
};
// Planet.cpp
#include "Planet.h"

Planet::Planet() {}
Planet::~Planet() {}

void Print() {} // Deliberately forgot to prefix `Planet::`
# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project (space_engine)
add_library (CelestialBody SHARED CelestialBody.cpp)
add_library (Planet SHARED Planet.cpp)
target_include_directories (CelestialBody PRIVATE ${CMAKE_CURRENT_LIST_DIR})  
target_include_directories (Planet PRIVATE ${CMAKE_CURRENT_LIST_DIR})    
target_link_libraries (Planet PUBLIC CelestialBody)

# hardened linker flags to catch undefined symbols
target_link_options(Planet 
    PRIVATE 
    -Wl,--as-needed
    -Wl,--no-undefined
)

我們得到了我們最喜歡的錯誤。

$ mkdir build
$ cd build
$ cmake ..
$ make
[ 50%] Built target CelestialBody
Scanning dependencies of target Planet
[ 75%] Building CXX object CMakeFiles/Planet.dir/Planet.cpp.o
[100%] Linking CXX shared library libPlanet.so
/usr/bin/ld: CMakeFiles/Planet.dir/Planet.cpp.o: in function `Planet::Planet()':
Planet.cpp:(.text+0x1b): undefined reference to `vtable for Planet'
/usr/bin/ld: CMakeFiles/Planet.dir/Planet.cpp.o: in function `Planet::~Planet()':
Planet.cpp:(.text+0x3d): undefined reference to `vtable for Planet'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/Planet.dir/build.make:104: libPlanet.so] Error 1
make[1]: *** [CMakeFiles/Makefile2:97: CMakeFiles/Planet.dir/all] Error 2
make: *** [Makefile:103: all] Error 2

我在Planet.cpp所做的當然應該用這個技巧解決

  1. 看看你的類定義。 找到第一個非純虛函數(不是“= 0”)並且您提供其定義(不是“= default”)的第一個非內聯虛函數。

來自JaMIT的回答。 如果有其他人嘗試了以上所有方法但沒有任何效果,那么也許您也像我一樣,不小心忘記在一個或多個成員函數前添加<ClassName>::前綴。

要么我需要檢查一下我的眼睛,要么我需要睡覺。

我在嘗試實現抽象工廠模式時也遇到了這個問題,但忘記鏈接一些庫。 因此,如果沒有任何幫助,請檢查是否鏈接了所有必需的庫

FWIW 我能夠避免這樣的錯誤:

 ld: /usr/local/lib/libvmaf.a(svm.cpp.o):(.data.rel.ro._ZTI7QMatrix[_ZTI7QMatrix]+0x0): undefined reference to `vtable for __cxxabiv1::__class_type_info'

將“C”項目與“C++”項目中的 static 庫鏈接時,通過將-lstdc++添加到鏈接參數。 所以它是gcc -lstdc++現在它可以工作了。

最常見的方法是將-lstdc++添加到庫 pkgconfig.pc 文件庫列表中。 或改為鏈接 g++。

我收到此錯誤僅僅是因為頭文件和實現文件中構造函數參數的名稱不同。 構造函數簽名為

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

我在實現中寫的內容始於

PointSet (const PointSet & pest, Parent * parent)

因此我不小心將“ pset”替換為“害蟲”。 編譯器抱怨這一個和另外兩個構造函數完全沒有錯誤。 我正在Ubuntu下使用g ++ 4.9.1版本。 在此派生類中定義虛擬析構函數沒有區別(它在基類中定義)。 如果不將構造函數的主體粘貼到頭文件中,從而在類中對其進行定義,我將永遠不會發現此錯誤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM