C++ 对 vtable 的未定义引用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3065154/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Undefined reference to vtable
提问by RyanG
When building my C++ program, I'm getting the error message
在构建我的 C++ 程序时,我收到错误消息
undefined reference to 'vtable...
对 'vtable... 的未定义引用
What is the cause of this problem? How do I fix it?
这个问题的原因是什么?我如何解决它?
It so happens that I'm getting the error for the following code (The class in question is CGameModule.) and I cannot for the life of me understand what the problem is. At first, I thought it was related to forgetting to give a virtual function a body, but as far as I understand, everything is all here. The inheritance chain is a little long, but here is the related source code. I'm not sure what other information I should provide.
碰巧我收到以下代码的错误(有问题的类是 CGameModule。)而且我一生都无法理解问题是什么。一开始,我以为是忘记给虚函数一个body有关,但据我所知,一切都在这里了。继承链有点长,但是这里有相关的源码。我不确定我应该提供哪些其他信息。
Note: The constructor is where this error is happening, it'd seem.
注意:构造函数是发生此错误的地方,看起来如此。
My code:
我的代码:
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;
};
Inherits from...
继承自...
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;
};
Which inherits from....
继承自......
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
采纳答案by RyanG
So, I've figured out the issue and it was a combination of bad logic and not being totally familiar with the automake/autotools world. I was adding the correct files to my Makefile.am template, but I wasn't sure which step in our build process actually created the makefile itself. So, I was compiling with an old makefile that had no idea about my new files whatsoever.
所以,我已经解决了这个问题,这是错误的逻辑和对 automake/autotools 世界不完全熟悉的组合。我正在将正确的文件添加到我的 Makefile.am 模板中,但我不确定我们构建过程中的哪一步实际创建了 makefile 本身。因此,我正在使用一个旧的 makefile 进行编译,该文件对我的新文件一无所知。
Thanks for the responses and the link to the GCC FAQ. I will be sure to read that to avoid this problem occurring for a real reason.
感谢您的回复和 GCC 常见问题的链接。我一定会阅读它,以避免由于真正的原因而发生此问题。
回答by Alexandre Hamez
回答by Dan
For what it is worth, forgetting a body on a virtual destructor generates the following:
值得一提的是,忘记虚拟析构函数上的主体会生成以下内容:
undefined reference to `vtable for CYourClass'.
未定义对 CYourClass 的 vtable 的引用。
I am adding a note because the error message is deceptive. (This was with gcc version 4.6.3.)
我添加注释是因为错误消息具有欺骗性。(这是使用 gcc 版本 4.6.3。)
回答by gluk47
If you are using Qt, try rerunning qmake. If this error is in the widget's class, qmake might have failed to notice that the ui class vtable should be regenerated. This fixed the issue for me.
如果您使用的是 Qt,请尝试重新运行 qmake。如果这个错误出现在小部件的类中,qmake 可能没有注意到应该重新生成 ui 类 vtable。这为我解决了这个问题。
回答by Srinivasa Lakkaraju
Undefined reference to vtable may occur due to the following situation also. Just try this:
由于以下情况也可能发生对 vtable 的未定义引用。试试这个:
Class A Contains:
A类包含:
virtual void functionA(parameters)=0;
virtual void functionB(parameters);
Class B Contains:
B类包含:
- The definition for the above functionA.
- The definition for the above functionB.
- 上述函数的定义A.
- 上述函数的定义 B.
Class C Contains: Now you're writing a Class C in which you are going to derive it from Class A.
C 类包含:现在您正在编写一个 C 类,您将在其中从 A 类派生它。
Now if you try to compile you will get Undefined reference to vtable for Class C as error.
现在,如果您尝试编译,您将收到 Undefined reference to vtable for Class C 作为错误。
Reason:
原因:
functionA
is defined as pure virtual and its definition is provided in Class B.
functionB
is defined as virtual (NOT PURE VIRTUAL) so it tries to find its definition in Class A itself but you provided its definition in Class B.
functionA
被定义为纯虚拟,其定义在 B 类中提供。
functionB
被定义为虚拟(非纯虚拟),因此它试图在 A 类本身中找到它的定义,但您在 B 类中提供了它的定义。
Solution:
解决方案:
- Make function B as pure virtual (if you have requirement like that)
virtual void functionB(parameters) =0;
(This works it is Tested) - Provide Definition for functionB in Class A itself keeping it as virtual . (Hope it works as I didn't try this)
- 使函数 B 成为纯虚拟的(如果您有这样的要求)
virtual void functionB(parameters) =0;
(这是经过测试的) - 为 Class A 中的 functionB 提供定义,将其保持为 virtual 。(希望它有效,因为我没有尝试过)
回答by Will
I simply got this error because my cpp file was not in the makefile.
我只是收到这个错误,因为我的 cpp 文件不在 makefile 中。
回答by JaMiT
What is a vtable
?
什么是vtable
?
It might be useful to know what the error message is talking about before trying to fix it. I'll start at a high level, then work down to some more details. That way people can skip ahead once they are comfortable with their understanding of vtables. …and there goes a bunch of people skipping ahead right now. :) For those sticking around:
在尝试修复它之前了解错误消息所谈论的内容可能会很有用。我将从高层次开始,然后深入研究更多细节。这样,一旦人们对 vtable 的理解感到满意,他们就可以跳过。......现在有很多人跳过。:) 对于那些坚持的人:
A vtable is basically the most common implementation of polymorphismin C++. When vtables are used, every polymorphic class has a vtable somewhere in the program; you can think of it as a (hidden) static
data member of the class. Every object of a polymorphic class is associated with the vtable for its most-derived class. By checking this association, the program can work its polymorphic magic. Important caveat:a vtable is an implementation detail. It is not mandated by the C++ standard, even though most (all?) C++ compilers use vtables to implement polymorphic behavior. The details I am presenting are either typical or reasonable approaches. Compilers are allowed to deviate from this!
vtable 基本上是C++ 中最常见的多态实现。使用 vtable 时,每个多态类在程序中的某处都有一个 vtable;您可以将其视为类的(隐藏)数据成员。多态类的每个对象都与其最派生类的 vtable 相关联。通过检查这种关联,程序可以发挥它的多态魔法。重要警告:vtable 是一个实现细节。尽管大多数(全部?)C++ 编译器使用 vtable 来实现多态行为,但 C++ 标准并未强制要求这样做。我提出的细节要么是典型的,要么是合理的。允许编译器偏离这一点!static
Each polymorphic object has a (hidden) pointer to the vtable for the object's most-derived class (possibly multiple pointers, in the more complex cases). By looking at the pointer, the program can tell what the "real" type of an object is (except during construction, but let's skip that special case). For example, if an object of type A
does not point to the vtable of A
, then that object is actually a sub-object of something derived from A
.
每个多态对象都有一个(隐藏的)指向该对象派生最多的类的 vtable 指针(在更复杂的情况下可能是多个指针)。通过查看指针,程序可以判断对象的“真实”类型是什么(构造期间除外,但让我们跳过那个特殊情况)。例如,如果 type 的对象A
不指向 的 vtable A
,那么该对象实际上是从 派生的某个对象的子对象A
。
The name "vtable" comes from "virtual function table". It is a table that stores pointers to (virtual) functions. A compiler chooses its convention for how the table is laid out; a simple approach is to go through the virtual functions in the order they are declared within class definitions. When a virtual function is called, the program follows the object's pointer to a vtable, goes to the entry associated with the desired function, then uses the stored function pointer to invoke the correct function. There are various tricks for making this work, but I won't go into those here.
“虚函数表”这个名字来源于“ virtual功能表”。它是一个存储指向(虚拟)函数的指针的表。编译器选择它的表格布局约定;一个简单的方法是按照它们在类定义中声明的顺序遍历虚函数。当一个虚函数被调用时,程序跟随对象的指针指向一个虚表,转到与所需函数关联的入口,然后使用存储的函数指针调用正确的函数。有各种各样的技巧来完成这项工作,但我不会在这里讨论这些。
Where/when is a vtable
generated?
在哪里/何时vtable
生成?
A vtable is automatically generated (sometimes called "emitted") by the compiler. A compiler could emit a vtable in every translation unit that sees a polymorphic class definition, but that would usually be unnecessary overkill. An alternative (used by gcc, and probably by others) is to pick a single translation unit in which to place the vtable, similar to how you would pick a single source file in which to put a class' static data members. If this selection process fails to pick any translation units, then the vtable becomes an undefined reference. Hence the error, whose message is admittedly not particularly clear.
编译器会自动生成(有时称为“发出”)一个 vtable。编译器可以在看到多态类定义的每个翻译单元中发出一个 vtable,但这通常是不必要的矫枉过正。另一种方法(由 gcc 使用,也可能由其他人使用)是选择一个单独的翻译单元来放置 vtable,类似于选择一个源文件来放置类的静态数据成员的方式。如果此选择过程未能选择任何翻译单元,则 vtable 将成为未定义的引用。因此出现了错误,其信息无疑不是特别清楚。
Similarly, if the selection process does pick a translation unit, but that object file is not provided to the linker, then the vtable becomes an undefined reference. Unfortunately, the error message can be even less clear in this case than in the case where the selection process failed. (Thanks to the answerers who mentioned this possibility. I probably would have forgotten it otherwise.)
类似地,如果选择过程确实选择了一个翻译单元,但该目标文件没有提供给链接器,则 vtable 成为未定义的引用。不幸的是,与选择过程失败的情况相比,这种情况下的错误消息甚至可能更不清楚。(感谢提到这种可能性的回答者。否则我可能会忘记它。)
The selection process used by gcc makes sense if we start with the tradition of devoting a (single) source file to each class that needs one for its implementation. It would be nice to emit the vtable when compiling that source file. Let's call that our goal. However, the selection process needs to work even if this tradition is not followed. So instead of looking for the implementation of the entire class, let's look for the implementation of a specific member of the class. If tradition is followed – and if that member is in fact implemented– then this achieves the goal.
如果我们从将一个(单个)源文件专门用于每个需要一个源文件来实现的类的传统开始,那么 gcc 使用的选择过程是有意义的。在编译该源文件时发出 vtable 会很好。让我们称之为我们的目标。然而,即使不遵循这一传统,选择过程也需要进行。因此,与其查找整个类的实现,不如查找类的特定成员的实现。如果遵循传统——如果该成员实际上得到了实施——那么这就实现了目标。
The member selected by gcc (and potentially by other compilers) is the first non-inline virtual function that is not pure virtual. If you are part of the crowd that declares constructors and destructors before other member functions, then that destructor has a good chance of being selected. (You did remember to make the destructor virtual, right?) There are exceptions; I'd expect that the most common exceptions are when an inline definition is provided for the destructor and when the default destructor is requested (using "= default
").
gcc(也可能是其他编译器)选择的成员是第一个不是纯虚的非内联虚函数。如果您是在其他成员函数之前声明构造函数和析构函数的人群中的一员,那么该析构函数很有可能被选中。(您确实记得将析构函数设为虚拟,对吗?)也有例外;我希望最常见的例外是为析构函数提供内联定义以及请求默认析构函数时(使用“ = default
”)。
The astute might notice that a polymorphic class is allowed to provide inline definitions for all of its virtual functions. Doesn't that cause the selection process to fail? It does in older compilers. I've read that the latest compilers have addressed this situation, but I do not know relevant version numbers. I could try looking this up, but it's easier to either code around it or wait for the compiler to complain.
精明的人可能会注意到,允许多态类为其所有虚函数提供内联定义。这不会导致选择过程失败吗?它在较旧的编译器中。我读过最新的编译器已经解决了这种情况,但我不知道相关的版本号。我可以尝试查找它,但是围绕它编写代码或等待编译器抱怨更容易。
In summary, there are three key causes of the "undefined reference to vtable" error:
综上所述,“undefined reference to vtable”错误主要有以下三个原因:
- A member function is missing its definition.
- An object file is not being linked.
- All virtual functions have inline definitions.
- 成员函数缺少其定义。
- 未链接目标文件。
- 所有虚函数都有内联定义。
These causes are by themselves insufficient to cause the error on their own. Rather, these are what you would address to resolve the error. Do not expect that intentionally creating one of these situations will definitely produce this error; there are other requirements. Do expect that resolving these situations will resolve this error.
这些原因本身不足以导致错误。相反,这些是您要解决的错误。不要指望故意制造这些情况之一肯定会产生此错误;还有其他要求。请期待解决这些情况将解决此错误。
(OK, number 3 might have been sufficient when this question was asked.)
(好吧,当问到这个问题时,数字 3 可能就足够了。)
How to fix the error?
如何修复错误?
Welcome back people skipping ahead! :)
欢迎回来的人跳过!:)
- Look at your class definition. Find the first non-inline virtual function that is not pure virtual (not "
= 0
") and whose definition you provide (not "= default
").- If there is no such function, try modifying your class so there is one. (Error possibly resolved.)
- See also the answer by Philip Thomasfor a caveat.
- Find the definition for that function. If it is missing, add it! (Error possibly resolved.)
- See also the answer by RedSpikeyThingfor a caveat.
- Check your link command. If it does not mention the object file with that function's definition, fix that! (Error possibly resolved.)
- Repeat steps 2 and 3 for each virtual function, then for each non-virtual function, until the error is resolved. If you're still stuck, repeat for each static data member.
- 看看你的类定义。找到第一个非纯虚函数(不是“
= 0
”)并且您提供其定义(不是“= default
”)的非内联虚函数。- 如果没有这样的功能,请尝试修改您的类,以便有一个。(错误可能已解决。)
- 另请参阅Philip Thomas 的回答以获取警告。
- 找到该函数的定义。如果缺少,请添加它!(错误可能已解决。)
- 另请参阅RedSpikeyThing 的回答以获取警告。
- 检查您的链接命令。如果它没有提到具有该函数定义的目标文件,请修复它!(错误可能已解决。)
- 对每个虚函数重复步骤 2 和 3,然后对每个非虚函数重复步骤 2 和 3,直到错误得到解决。如果您仍然卡住,请对每个静态数据成员重复此操作。
Example
The details of what to do can vary, and sometimes branch off into separate questions (like What is an undefined reference/unresolved external symbol error and how do I fix it?). I will, though, provide an example of what to do in a specific case that might befuddle newer programmers.
示例
要做什么的细节可能会有所不同,有时会分为单独的问题(例如什么是未定义的引用/未解决的外部符号错误以及如何修复它?)。不过,我将提供一个示例,说明在可能会迷惑新程序员的特定情况下该怎么做。
Step 1 mentions modifying your class so that it has a function of a certain type. If the description of that function went over your head, you might be in the situation I intend to address. Keep in mind that this is a way to accomplish the goal; it is not the only way, and there easily could be better ways in your specific situation. Let's call your class A
. Is your destructor declared (in your class definition) as either
第 1 步提到修改您的类,使其具有某种类型的功能。如果该功能的描述超出了您的脑海,您可能处于我打算解决的情况。请记住,这是实现目标的一种方式;这不是唯一的方法,在您的特定情况下很容易有更好的方法。让我们打电话给你的班级A
。您的析构函数是否(在您的类定义中)声明为
virtual ~A() = default;
or
或者
virtual ~A() {}
? If so, two steps will change your destructor into the type of function we want. First, change that line to
? 如果是这样,两个步骤会将您的析构函数更改为我们想要的函数类型。首先,将该行更改为
virtual ~A();
Second, put the following line in a source file that is part of your project (preferably the file with the class implementation, if you have one):
其次,将以下行放在作为项目一部分的源文件中(最好是具有类实现的文件,如果有的话):
A::~A() {}
That makes your (virtual) destructor non-inline and not generated by the compiler. (Feel free to modify things to better match your code formatting style, such as adding a header comment to the function definition.)
这使您的(虚拟)析构函数是非内联的,而不是由编译器生成的。(随意修改内容以更好地匹配您的代码格式样式,例如向函数定义添加标题注释。)
回答by Shital Shah
There is lot of speculation going on in various answers here. I'll below give a fairly minimal code that reproduces this error and explain why it is occuring.
这里的各种答案都有很多猜测。我将在下面给出一个相当少的代码来重现这个错误并解释它发生的原因。
Fairly Minimal Code to Reproduce This Error
重现此错误的相当少的代码
IBase.hpp
数据库文件
#pragma once
class IBase {
public:
virtual void action() = 0;
};
Derived.hpp
衍生.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
我的类.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;
}
You can compile this using GCC like this:
你可以像这样使用 GCC 编译它:
g++ -std=c++11 -o a.out myclass.cpp Derived.cpp
You can now reproduce the error by removing = 0
in IBase.hpp. I get this error:
您现在可以通过= 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
Explanation
解释
Notice that above code does not require any virtual destructors, constructors or any other extra files for compile to be successful (although you should have them).
请注意,上面的代码不需要任何虚拟析构函数、构造函数或任何其他额外的文件来使编译成功(尽管您应该拥有它们)。
The way to understand this error is as follows: Linker is looking for constructor of IBase. This it will need it for the constructor of Derived. However as Derived overrides methods from IBase, it has vtable attached to it that will reference IBase. When linker says "undefined reference to vtable for IBase" it basically means that Derived has vtable reference to IBase but it can't find any compiled object code of IBase to look up to. So the bottom line is that class IBase has declarations without implementations. This means a method in IBase is declared as virtual but we forgot to mark it as pure virtual OR provide its definition.
理解这个错误的方法如下: Linker 正在寻找 IBase 的构造函数。这将需要它用于 Derived 的构造函数。但是,由于 Derived 覆盖了来自 IBase 的方法,因此它附加了将引用 IBase 的 vtable。当链接器说“未定义对 IBase 的 vtable 的引用”时,它基本上意味着 Derived 具有对 IBase 的 vtable 引用,但它找不到任何 IBase 的编译目标代码来查找。所以底线是类 IBase 有没有实现的声明。这意味着 IBase 中的方法被声明为虚拟,但我们忘记将其标记为纯虚拟或提供其定义。
Parting Tip
分离提示
If all else fails then one way to debug this error is to build minimal program that does compile and then keep changing it so it gets to the state you want. In between, keep compiling to see when it starts to fail.
如果所有其他方法都失败了,那么调试此错误的一种方法是构建可以编译的最小程序,然后不断更改它,使其达到您想要的状态。在两者之间,继续编译以查看何时开始失败。
Note on ROS and Catkin build system
关于 ROS 和 Catkin 构建系统的说明
If you were compiling above set of classes in ROS using catkin build system then you will need following lines in CMakeLists.txt:
如果您使用 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})
The first line basically says that we want to make an executable named myclass and the code to build this can be found files that follows. One of these files should have main(). Notice that you don't have to specify .hpp files anywhere in CMakeLists.txt. Also you don't have to specify Derived.cpp as library.
第一行基本上是说我们想要创建一个名为 myclass 的可执行文件,构建它的代码可以在后面的文件中找到。这些文件之一应该有 main()。请注意,您不必在 CMakeLists.txt 中的任何位置指定 .hpp 文件。此外,您不必将 Derived.cpp 指定为库。
回答by Philip Thomas
I just ran into another cause for this error that you can check for.
我刚刚遇到了您可以检查的此错误的另一个原因。
The base class defined a pure virtual functionas:
基类定义了一个纯虚函数:
virtual int foo(int x = 0);
And the subclass had
子类有
int foo(int x) override;
The problem was the typo that the "=0"
was supposed to be outside of the parenthesis:
问题是"=0"
应该在括号之外的错字:
virtual int foo(int x) = 0;
So, in case you're scrolling this far down, you probably didn't find the answer - this is something else to check for.
所以,如果你向下滚动这么远,你可能没有找到答案 - 这是其他需要检查的东西。
回答by Hazok
This can happen quite easily if you forget to link to the object file that has the definition.
如果您忘记链接到具有定义的目标文件,这很容易发生。