未定义的vtable参考

Rya*_*anG 315 c++ gcc g++

所以,我变得臭名昭着

未定义的引用'vtable ...

以下代码的错误(有问题的类是CGameModule.)我不能为我的生活理解问题所在.起初,我认为这与忘记给虚拟功能一个身体有关,但据我所知,一切都在这里.继承链有点长,但这里是相关的源代码.我不确定我应该提供哪些其他信息.

注意:构造函数是发生此错误的地方,看起来如此.

我的代码:

class CGameModule : public CDasherModule {
 public:
  CGameModule(Dasher::CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
  : CDasherModule(pEventHandler, pSettingsStore, iID, 0, szName)
  { 
      g_pLogger->Log("Inside game module constructor");   
      m_pInterface = pInterface; 
  }

  virtual ~CGameModule() {};

  std::string GetTypedTarget();

  std::string GetUntypedTarget();

  bool DecorateView(CDasherView *pView) {
      //g_pLogger->Log("Decorating the view");
      return false;
  }

  void SetDasherModel(CDasherModel *pModel) { m_pModel = pModel; }


  virtual void HandleEvent(Dasher::CEvent *pEvent); 

 private:



  CDasherNode *pLastTypedNode;


  CDasherNode *pNextTargetNode;


  std::string m_sTargetString;


  size_t m_stCurrentStringPos;


  CDasherModel *m_pModel;


  CDasherInterfaceBase *m_pInterface;
};
Run Code Online (Sandbox Code Playgroud)

继承自......

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;
};
Run Code Online (Sandbox Code Playgroud)

哪个继承自....

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
Run Code Online (Sandbox Code Playgroud)

Ale*_*mez 386

GCC FAQ上有一个条目:

解决方案是确保定义所有非纯的虚方法.请注意,即使声明为pure-virtual [class.dtor]/7,也必须定义析构函数.

  • FFS,为什么编译器没有检查并打印错误消息? (123认同)
  • 显然,这只能由链接器而不是编译器发现. (18认同)
  • `nm -C CGameModule.o | grep CGameModule ::`将列出定义的方法,假设您的整个类实现进入逻辑对象文件.您可以将其与定义为虚拟的内容进行比较,以找出您错过的内容. (16认同)
  • 如果链接器能够生成更友好的错误消息,也许会指定哪个方法未定义或错误定义的提示,那就太好了。 (12认同)
  • 就我而言,我们有没有析构函数实现的抽象类。我不得不把空实现 ~MyClass(){} (2认同)
  • 当您尝试链接的对象在存档(libxyz.a 文件)中丢失时,您可能会收到这样的错误:对“vtable for objfilename”的未定义引用 (2认同)
  • 如果您创建抽象类的子类并且忘记实现您覆盖的某些内容,也可能会发生这种情况。 (2认同)

Dan*_*Dan 154

对于它的价值,忘记虚拟析构函数上的主体会生成以下内容:

未定义引用`vtable for CYourClass'.

我正在添加注释,因为错误消息具有欺骗性.(这是gcc版本4.6.3.)

  • 我必须明确地将我的空虚拟析构函数的主体放在定义文件(*.cc)中.将它放在标题中仍然给了我错误. (18认同)
  • 请注意,一旦我将虚拟析构函数添加到实现文件中,然后gcc告诉我实际错误,这是另一个函数上缺少的主体. (4认同)
  • @PopcornKing 我看到了同样的问题。即使在头文件中定义 `~Destructor = default;` 也无济于事。是否有针对 gcc 提交的记录错误? (2认同)

JaM*_*MiT 77

TL;DR - 解释为什么 vtable 可能会丢失以及如何修复它。答案很长,因为它解释了为什么编译器可能会忘记创建 vtable。(编辑)

什么是vtable

在尝试修复它之前了解错误消息所谈论的内容可能会很有用。我将从高层次开始,然后深入研究更多细节。这样,一旦人们对 vtable 的理解感到满意,他们就可以跳过。......现在有很多人跳过。:) 对于那些坚持的人:

vtable 基本上是C++ 中最常见的多态 实现。使用 vtable 时,每个多态类在程序中的某处都有一个 vtable;您可以将其视为类的(隐藏)数据成员。多态类的每个对象都与其最派生类的 vtable 相关联。通过检查这种关联,程序可以发挥它的多态魔法。重要警告: vtable 是一个实现细节。尽管大多数(全部?)C++ 编译器使用 vtable 来实现多态行为,但 C++ 标准并未强制要求这样做。我提出的细节要么是典型的,要么是合理的。允许编译器偏离这一点!static

每个多态对象都有一个(隐藏的)指向该对象派生最多的类的 vtable 指针(在更复杂的情况下可能是多个指针)。通过查看指针,程序可以判断对象的“真实”类型是什么(构造期间除外,但让我们跳过那个特殊情况)。例如,如果 type 的对象A不指向 的 vtable 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;
Run Code Online (Sandbox Code Playgroud)

或者

virtual ~A() {}
Run Code Online (Sandbox Code Playgroud)

? 如果是这样,两个步骤会将您的析构函数更改为我们想要的函数类型。首先,将该行更改为

virtual ~A();
Run Code Online (Sandbox Code Playgroud)

其次,将以下行放在作为项目一部分的源文件中(最好是具有类实现的文件,如果有的话):

A::~A() {}
Run Code Online (Sandbox Code Playgroud)

这使您的(虚拟)析构函数非内联,而不是由编译器生成。(随意修改内容以更好地匹配您的代码格式样式,例如向函数定义添加标题注释。)

  • 哦,太棒了!对于非常详细且范围非常广泛的解释。 (2认同)

Rya*_*anG 52

所以,我已经找到了问题,这是一个错误的逻辑组合,并没有完全熟悉automake/autotools世界.我正在将正确的文件添加到我的Makefile.am模板中,但我不确定构建过程中的哪一步实际创建了makefile本身.所以,我正在使用一个不知道我的新文件的旧makefile进行编译.

感谢您的回复和GCC常见问题解答的链接.我一定会读到这一点,以避免出现这个问题,原因很简单.

  • 对于Qt用户:如果忘记复制标题,则可能会出现同样的错误. (63认同)
  • 简而言之:.cpp刚刚未包含在构建中.错误消息确实具有误导性. (41认同)
  • -1这可能是您问题的解决方案,但它不是原始问题的答案.正确答案就是您没有提供带有所需符号的目标文件.为什么你没有提供它们是另一个故事. (13认同)
  • @Walter:实际上这是我正在寻找的确切答案.其他是显而易见的,因而无益. (11认同)
  • 我想你应该接受Alexandre Hamez的答案.搜索此错误的人很可能需要他的解决方案而不是您的解决方案. (8认同)
  • 我同意Edgar的观点,如果你错过了一个虚函数定义,你可能会遇到这样的错误,但有时,真正的答案确实是真正的答案. (3认同)

glu*_*k47 46

如果您使用的是Qt,请尝试重新运行qmake.如果此错误发生在窗口小部件的类中,则qmake可能未注意到应重新生成ui类vtable.这为我解决了这个问题.

  • 我刚刚删除了整个文件夹,它也有效. (2认同)
  • 以为“ Rebuild”会自动重新运行qmake……显然不是。我按照您的建议执行了“ Run qmake”,然后执行了“ Rebuild”,它解决了我的问题。 (2认同)

小智 42

由于以下情况,可能会发生对vtable的未定义引用.试试这个:

A类包含:

virtual void functionA(parameters)=0; 
virtual void functionB(parameters);
Run Code Online (Sandbox Code Playgroud)

B类包含:

  1. 上述功能的定义A.
  2. 上述functionB的定义.

C类包含:现在您正在编写一个C类,您将从A类中派生它.

现在,如果您尝试编译,您将获得对C类的vtable的未定义引用作为错误.

原因:

functionA定义为纯虚拟,其定义在类B中提供. functionB定义为虚拟(NOT PURE VIRTUAL),因此它尝试在类A中找到它的定义,但是您在类B中提供了它的定义.

解:

  1. 将函数B设为纯虚拟(如果你有这样的要求) virtual void functionB(parameters) =0; (这是有效的)
  2. 为A类中的functionB提供定义,使其保持为虚拟.(希望它有效,因为我没试过这个)


Wil*_*ill 42

我只是得到了这个错误,因为我的cpp文件不在makefile中.


Shi*_*hah 18

这里有各种各样的答案,有很多猜测.我将在下面给出一个相当少的代码来重现这个错误并解释它为什么会发生.

相当简单的代码重现此错误

IBase.hpp

#pragma once

class IBase {
    public:
        virtual void action() = 0;
};
Run Code Online (Sandbox Code Playgroud)

Derived.hpp

#pragma once

#include "IBase.hpp"

class Derived : public IBase {
    public:
        Derived(int a);
        void action() override;
};
Run Code Online (Sandbox Code Playgroud)

Derived.cpp

#include "Derived.hpp"
Derived::Derived(int a) { }
void Derived::action() {}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

您可以使用GCC编译它,如下所示:

g++ -std=c++11 -o a.out myclass.cpp Derived.cpp
Run Code Online (Sandbox Code Playgroud)

您现在可以通过= 0在IBase.hpp中删除来重现错误 .我收到此错误:

~/.../catkin_ws$ g++ -std=c++11 -o /tmp/m.out /tmp/myclass.cpp /tmp/Derived.cpp
/tmp/cclLscB9.o: In function `IBase::IBase(IBase const&)':
myclass.cpp:(.text._ZN5IBaseC2ERKS_[_ZN5IBaseC5ERKS_]+0x13): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o: In function `IBase::IBase()':
Derived.cpp:(.text._ZN5IBaseC2Ev[_ZN5IBaseC5Ev]+0xf): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o:(.rodata._ZTI7Derived[_ZTI7Derived]+0x10): undefined reference to `typeinfo for IBase'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

说明

请注意,上面的代码不需要任何虚拟析构函数,构造函数或任何其他额外的文件,以便编译成功(尽管您应该拥有它们).

理解这个错误的方法如下:链接器正在寻找IBase的构造函数.这将需要它为Derived的构造函数.但是,由于Derived会覆盖IBase中的方法,因此它附加了vtable,它将引用IBase.当链接器说"对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})
Run Code Online (Sandbox Code Playgroud)

第一行基本上说我们想要创建一个名为myclass的可执行文件,并且可以找到构建它的代码.其中一个文件应该有main().请注意,您不必在CMakeLists.txt中的任何位置指定.hpp文件.您也不必将Derived.cpp指定为库.


小智 17

我刚刚遇到了你可以检查的这个错误的另一个原因.

基类将纯虚函数定义为:

virtual int foo(int x = 0);
Run Code Online (Sandbox Code Playgroud)

并且子类有

int foo(int x) override;
Run Code Online (Sandbox Code Playgroud)

问题是错误"=0"应该是在括号之外:

virtual int foo(int x) = 0;
Run Code Online (Sandbox Code Playgroud)

所以,如果你滚动这么远,你可能没有找到答案 - 这是另外要检查的东西.

  • 哈哈,C++ 给了你太多的方法,让你不小心搬起石头砸自己的脚。这看起来是我很可能犯的错误。 (2认同)

Haz*_*zok 12

如果您忘记链接到具有该定义的目标文件,则很容易发生这种情况.


小智 11

GNU C++编译器必须决定在何处放置vtable以防止跨多个编译单元的对象的虚函数的定义(例如,一些对象虚拟函数定义在.cpp文件中,其他在另一个中. cpp文件,等等).

编译器选择将它放在与vtable定义第一个声明的虚函数的位置相同的位置.

现在,如果由于某种原因忘记为对象中声明的第一个虚函数提供定义(或者错误地忘记在链接阶段添加编译对象),则会出现此错误.

作为一个副作用,请注意,只有这个特定的虚函数,你不会得到传统的链接器错误,就像你缺少函数foo.


Vic*_*ist 8

不要过马路但是.如果你正在处理继承,第二次google点击是我错过的,即.应该定义所有虚拟方法.

如:

virtual void fooBar() = 0;
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅answare C++未定义的vtable和继承参考.刚刚意识到它已经在上面提到了,但是它可能对某人有帮助.


Sam*_*mmy 8

好的,解决方法是您可能错过了定义.请参阅下面的示例以避免vtable编译器错误:

// In the CGameModule.h

class CGameModule
{
public:
    CGameModule();
    ~CGameModule();

    virtual void init();
};

// In the CGameModule.cpp

#include "CGameModule.h"

CGameModule::CGameModule()
{

}

CGameModule::~CGameModule()
{

}

void CGameModule::init()    // Add the definition
{

}
Run Code Online (Sandbox Code Playgroud)


Ste*_*hen 7

  • 你确定CDasherComponent有一个析构函数的主体吗?它肯定不在这里 - 问题是它是否在.cc文件中.
  • 从样式的角度来看,CDasherModule应该明确定义它的析构函数virtual.
  • 看起来最后CGameModule有一个额外} 的(之后}; // for the class).
  • CGameModule被链接的是定义库CDasherModuleCDasherComponent


Seb*_*247 7

对于使用 CMakeList.txt 的 Qt 用户

在 CMakeLists.txt 中添加以下行:set(CMAKE_AUTOMOC ON)

正如 Chris Morler 所解释的,如果您忘记 moc 标头,您会收到此错误


ein*_*ica 7

这是 GCC 中的一个错误功能。也就是说,G++ 编译器本身不能抱怨未定义的虚拟方法,因为它们可以在其他地方定义。但是 - 它不存储有关哪些虚拟成员丢失的信息;它只存储一个 UND 定义的 vtable 符号,然后链接器会抱怨该符号。

相反,如果要列出缺失的成员,链接器可能会告诉您它们是什么。

针对 GCC 的此问题存在一个未解决的错误:bug 42540。不幸的是,它已经13岁了:-(


Dev*_*ght 5

也许缺少虚拟析构函数是影响因素?

virtual ~CDasherModule(){};
Run Code Online (Sandbox Code Playgroud)


Red*_*ing 5

这是我的第一个搜索结果,所以我想我要添加另一个东西来检查:确保虚函数的定义实际上在类上.就我而言,我有这个:

头文件:

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

class B : public A {
 public:
  void foo() override;
};
Run Code Online (Sandbox Code Playgroud)

在我的.cc文件中:

void foo() {
  ...
}
Run Code Online (Sandbox Code Playgroud)

这应该是读

void B::foo() {
}
Run Code Online (Sandbox Code Playgroud)