所以,我变得臭名昭着
未定义的引用'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,也必须定义析构函数.
Dan*_*Dan 154
对于它的价值,忘记虚拟析构函数上的主体会生成以下内容:
未定义引用`vtable for CYourClass'.
我正在添加注释,因为错误消息具有欺骗性.(这是gcc版本4.6.3.)
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”错误主要有以下三个原因:
这些原因本身不足以导致错误。相反,这些是您要解决的错误。不要指望故意制造这些情况之一肯定会产生此错误;还有其他要求。请期待解决这些情况将解决此错误。
(好吧,当问到这个问题时,数字 3 可能就足够了。)
欢迎回来的人跳过!:)
= 0
”)并且您提供其定义(不是“ = default
”)的第一个非内联虚函数。
ClassName::function_name
.示例
要做什么的细节可能会有所不同,有时会分为单独的问题(例如什么是未定义的引用/未解决的外部符号错误以及如何修复它?)。不过,我将提供一个示例,说明在可能会迷惑新程序员的特定情况下该做什么。
第 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)
这使您的(虚拟)析构函数非内联,而不是由编译器生成。(随意修改内容以更好地匹配您的代码格式样式,例如向函数定义添加标题注释。)
Rya*_*anG 52
所以,我已经找到了问题,这是一个错误的逻辑组合,并没有完全熟悉automake/autotools世界.我正在将正确的文件添加到我的Makefile.am模板中,但我不确定构建过程中的哪一步实际创建了makefile本身.所以,我正在使用一个不知道我的新文件的旧makefile进行编译.
感谢您的回复和GCC常见问题解答的链接.我一定会读到这一点,以避免出现这个问题,原因很简单.
glu*_*k47 46
如果您使用的是Qt,请尝试重新运行qmake.如果此错误发生在窗口小部件的类中,则qmake可能未注意到应重新生成ui类vtable.这为我解决了这个问题.
小智 42
由于以下情况,可能会发生对vtable的未定义引用.试试这个:
A类包含:
virtual void functionA(parameters)=0;
virtual void functionB(parameters);
Run Code Online (Sandbox Code Playgroud)
B类包含:
C类包含:现在您正在编写一个C类,您将从A类中派生它.
现在,如果您尝试编译,您将获得对C类的vtable的未定义引用作为错误.
原因:
functionA
定义为纯虚拟,其定义在类B中提供.
functionB
定义为虚拟(NOT PURE VIRTUAL),因此它尝试在类A中找到它的定义,但是您在类B中提供了它的定义.
解:
virtual void functionB(parameters) =0;
(这是有效的)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)
所以,如果你滚动这么远,你可能没有找到答案 - 这是另外要检查的东西.
小智 11
GNU C++编译器必须决定在何处放置vtable
以防止跨多个编译单元的对象的虚函数的定义(例如,一些对象虚拟函数定义在.cpp文件中,其他在另一个中. cpp文件,等等).
编译器选择将它放在与vtable
定义第一个声明的虚函数的位置相同的位置.
现在,如果由于某种原因忘记为对象中声明的第一个虚函数提供定义(或者错误地忘记在链接阶段添加编译对象),则会出现此错误.
作为一个副作用,请注意,只有这个特定的虚函数,你不会得到传统的链接器错误,就像你缺少函数foo.
不要过马路但是.如果你正在处理继承,第二次google点击是我错过的,即.应该定义所有虚拟方法.
如:
virtual void fooBar() = 0;
Run Code Online (Sandbox Code Playgroud)
有关详细信息,请参阅answare C++未定义的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
{
}
Run Code Online (Sandbox Code Playgroud)
CDasherComponent
有一个析构函数的主体吗?它肯定不在这里 - 问题是它是否在.cc文件中.CDasherModule
应该明确定义它的析构函数virtual
.CGameModule
有一个额外}
的(之后}; // for the class
).CGameModule
被链接的是定义库CDasherModule
和CDasherComponent
?对于使用 CMakeList.txt 的 Qt 用户
在 CMakeLists.txt 中添加以下行:set(CMAKE_AUTOMOC ON)
正如 Chris Morler 所解释的,如果您忘记 moc 标头,您会收到此错误
这是我的第一个搜索结果,所以我想我要添加另一个东西来检查:确保虚函数的定义实际上在类上.就我而言,我有这个:
头文件:
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)
归档时间: |
|
查看次数: |
468487 次 |
最近记录: |