在构建 C++ 程序时,我收到错误消息“未定义对‘vtable 的引用...”此问题的原因是什么?我该如何修复它?我恰好收到以下错误:
在构建 C++ 程序时,我收到错误消息
对“vtable…”未定义引用
导致此问题的原因是什么?我该如何修复它?
碰巧的是,我遇到了以下代码的错误(有问题的类是 CGameModule。)我无论如何也想不出问题出在哪里。起初,我认为这与忘记给虚拟函数提供主体有关,但据我所知,一切都在这里。继承链有点长,但这是相关的源代码。我不确定我还应该提供什么其他信息。
注意:看起来,这个错误是在构造函数中发生。
我的代码:
游戏模块
class CGameModule : public Dasher::CDasherComponent {
public:
CGameModule() : CDasherModule() {};
virtual ~CGameModule();
virtual void HandleEvent(Dasher::CEvent *pEvent);
};
游戏模块.cpp
#include "cgamemodule.h"
void CGameModule::HandleEvent(Dasher::CEvent *pEvent) {}
继承自....
namespace Dasher {
class CEvent;
class CDasherComponent;
};
class Dasher::CDasherComponent {
public:
CDasherComponent() {};
virtual ~CDasherComponent() {};
virtual void HandleEvent(Dasher::CEvent * pEvent) {};
};
TL;DR - 解释为什么 vtable 可能缺失以及如何修复它。答案很长,因为它解释了为什么编译器可能会忘记创建 vtable。(编辑)
vtable
?
在尝试修复之前,了解错误消息的内容可能会很有用。我会从高层次开始,然后再深入到一些细节。这样,一旦人们理解了 vtable,就可以跳过这一步。……现在有一大群人跳过了这一步。:) 对于那些坚持下来的人:
C++ 中 polymorphism 实现 。使用 vtable 时,每个多态类在程序中的某个位置都有一个 vtable;您可以将其视为类的(隐藏) static
数据成员。多态类的每个对象都与其派生类的 vtable 相关联。通过检查此关联,程序可以发挥其多态魔力。 重要警告: vtable 是一个实现细节。它不是 C++ 标准强制要求的,尽管大多数(所有?)C++ 编译器都使用 vtable 来实现多态行为。我介绍的细节是典型或合理的方法。 编译器可以偏离这一点!
每个多态对象都有一个(隐藏的)指针,指向该对象最外层派生类的 vtable(在更复杂的情况下,可能有多个指针)。通过查看指针,程序可以判断对象的“真实”类型是什么(构造期间除外,但我们先跳过这种特殊情况)。例如,如果类型的对象 A
没有指向 vtable A
,则该对象实际上是派生自的某个对象的子对象 A
.
\'虚拟表\' 这个名称来自 \' v 函数 table \'。它是一个存储指向(虚拟)函数的指针的表。编译器选择其表的布局惯例;一种简单的方法是按照类定义中声明的顺序遍历虚拟函数。当调用虚拟函数时,程序会按照对象的指针指向 vtable,转到与所需函数关联的条目,然后使用存储的函数指针调用正确的函数。有各种技巧可以实现这一点,但我不会在这里讨论这些技巧。
vtable
生成?
vtable 由编译器自动生成(有时称为“发出”)。编译器可以在每个看到多态类定义的翻译单元中发出 vtable,但这通常是不必要的。另一种选择( 由 gcc 使用 ,可能还有其他人使用)是选择一个翻译单元来放置 vtable,类似于选择单个源文件来放置类的静态数据成员。如果此选择过程未能选择任何翻译单元,则 vtable 将成为未定义的引用。因此会出现错误,其消息确实不是特别清楚。
类似地,如果选择过程确实选择了翻译单元,但未向链接器提供该目标文件,则 vtable 将成为未定义的引用。不幸的是,在这种情况下,错误消息可能比选择过程失败的情况更不清晰。 (感谢提到这种可能性的回答者。否则我可能会忘记它。)
如果我们从为每个需要实现的类专门提供一个(单个)源文件的传统开始,那么 gcc 使用的选择过程是有意义的。在编译该源文件时发出 vtable 会很好。我们称之为我们的目标。但是,即使不遵循这一传统,选择过程也需要发挥作用。因此,我们不要寻找整个类的实现,而是寻找类的特定成员的实现。如果遵循传统——并且 如果该成员确实得到实现 ——那么这就实现了目标。
gcc(也可能是其他编译器)选择的成员是第一个非纯虚拟的非内联虚拟函数。如果您是在其他成员函数之前声明构造函数和析构函数的人群中的一员,那么该析构函数很有可能被选中。(您确实记得将析构函数设为虚拟的,对吗?)有例外;我认为最常见的例外是当为析构函数提供内联定义时以及当请求默认析构函数时(使用 \' = default
\')。
精明的人可能会注意到,多态类被允许为其所有虚函数提供内联定义。这不会导致选择过程失败吗?在较旧的编译器中确实如此。我读到最新的编译器已经解决了这种情况,但我不知道相关的版本号。我可以尝试查找这个问题,但更简单的方法是绕过它或等待编译器发出抱怨。
总结一下,导致“未定义的对 vtable 的引用”错误的主要有三个原因:
这些原因本身不足以导致错误。相反,这些是解决错误需要解决的问题。不要指望故意制造这些情况之一就一定会产生此错误;还有其他要求。请期望解决这些情况将解决此错误。
(好吧,当问到这个问题时,数字 3 可能就足够了。)
欢迎跳过前面内容的人们回来!:)
=0
),也不是 =default
类定义中的默认函数 ( )。 ClassName::function_name
. inline
(somewhat uncommon for virtual functions), then unfortunately this is not the function you are looking for. For this answer, treat such a function as if it was defined inside the class definition. 具体该做什么可能会有所不同,有时会分成不同的问题(例如 什么是未定义的引用/未解析的外部符号错误以及如何修复它? )。不过,我将提供一个示例,说明在特定情况下该怎么做,这可能会让新程序员感到困惑。
步骤 1 提到修改您的类,使其具有特定类型的函数。如果您不明白该函数的描述,您可能正处于我打算解决的情况。请记住,这是实现目标的一种方法;它不是唯一的方法,在您的特定情况下,很容易有更好的方法。让我们称您的类为 A
。您的析构函数是否(在您的类定义中)声明为
virtual ~A() = default;
或者
virtual ~A() {}
?如果是这样,两个步骤将把您的析构函数更改为我们想要的函数类型。首先,将该行更改为
virtual ~A();
其次,将以下其中一行放入项目的源文件中(如果有的话,最好是包含类实现的文件):
A::~A() = default;
或者
A::~A() {}
这样,在将析构函数的定义移到类定义之外的同时,该析构函数仍保持虚拟状态。(您可以随意修改以更好地匹配您的代码格式样式,例如在函数定义中添加标题注释。)