8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

未定义对 vtable 的引用

AlgorithmAce 2月前

130 0

在构建 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) {};
};
帖子版权声明 1、本帖标题:未定义对 vtable 的引用
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由AlgorithmAce在本站《unit-testing》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我完全没有注意到错误消息指定了一个函数。它恰好是构造函数,所以我看到了我的类名,但没有建立联系。因此,构造函数抛出了这个错误。我会在我原来的帖子中添加这个细节。

  • 所以,我弄清楚了这个问题,这是由于逻辑错误和对 automake/autotools 世界不完全熟悉造成的。我向 Makefile.am 模板添加了正确的文件,但我不确定构建过程中的哪个步骤实际上创建了 makefile 本身。所以,我使用一个旧的 makefile 进行编译,而这个 makefile 对我的新文件一无所知。

    感谢您的回复和 GCC FAQ 链接。我一定会阅读它以避免因真实原因而发生此问题。

  • 不过我认为你应该接受 Alexandre Hamez 的答案。搜索此错误的人很可能需要他的解决方案,而不是你的解决方案。

  • -1 这可能是您问题的解决方案,但这不是原始问题的答案。正确的答案很简单,您没有提供包含所需符号的目标文件。为什么您没有提供它们是另一个故事。

  • @Walter:实际上,这正是我所寻找的答案。其他答案显而易见,因此没有帮助。

  • GCC FAQ 中 有一个相关条目:

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

    因此,需要为虚拟析构函数提供一个定义:

    virtual ~CDasherModule()
    { }
    
  • nm -C CGameModule.o | grep CGameModule:: 将列出已定义的方法,假设您的整个类实现都进入逻辑对象文件。您可以将其与定义为虚拟的内容进行比较,以找出您遗漏的内容。

  • Zeus 2月前 0 只看Ta
    引用 9

    在我的例子中,我们有一个没有析构函数实现的抽象类。我不得不把空的实现放到 ~MyClass(){} 中

  • 如果链接器可以生成更友好的错误消息,也许可以提示哪种方法未定义或定义错误,那就太好了。

  • 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 的引用”错误的主要有三个原因:

    1. 成员函数缺少定义。
    2. 目标文件尚未被链接。
    3. 所有虚函数都有内联定义。

    这些原因本身不足以导致错误。相反,这些是解决错误需要解决的问题。不要指望故意制造这些情况之一就一定会产生此错误;还有其他要求。请期望解决这些情况将解决此错误。

    (好吧,当问到这个问题时,数字 3 可能就足够了。)

    如何修复错误?

    欢迎跳过前面内容的人们回来!:)

    1. 查看类定义。查找类定义中未定义的第一个虚函数,该虚函数不是纯虚函数 ( =0 ),也不是 =default 类定义中的默认函数 ( )。
      • If there is no such function, try modifying your class so there is one. (Error possibly resolved.)
      • See also the answer by Philip Thomas for a caveat.
    2. 找到该函数的定义。如果缺失,请添加! (错误可能已解决。)
      • Since the function definition is outside the class definition, make sure the function definition uses a qualified name, as in ClassName::function_name .
      • If the function definition uses the keyword 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.
    3. 检查您的链接命令。如果它没有提及具有该函数定义的目标文件,请修复它! (错误可能已解决。)
    4. 对每个虚拟函数重复步骤 2 和 3,然后对每个非虚拟函数重复步骤 2 和 3,直到错误解决。如果仍然卡住,请对每个静态数据成员重复此操作。

    例子

    具体该做什么可能会有所不同,有时会分成不同的问题(例如 什么是未定义的引用/未解析的外部符号错误以及如何修复它? )。不过,我将提供一个示例,说明在特定情况下该怎么做,这可能会让新程序员感到困惑。

    步骤 1 提到修改您的类,使其具有特定类型的函数。如果您不明白该函数的描述,您可能正处于我打算解决的情况。请记住,这是实现目标的一种方法;它不是唯一的方法,在您的特定情况下,很容易有更好的方法。让我们称您的类为 A 。您的析构函数是否(在您的类定义中)声明为

    virtual ~A() = default;
    

    或者

    virtual ~A() {}
    

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

    virtual ~A();
    

    其次,将以下其中一行放入项目的源文件中(如果有的话,最好是包含类实现的文件):

    A::~A() = default;
    

    或者

    A::~A() {}
    

    这样,在将析构函数的定义移到类定义之外的同时,该析构函数仍保持虚拟状态。(您可以随意修改以更好地匹配您的代码格式样式,例如在函数定义中添加标题注释。)

  • 你是我的英雄。我们有一个相当大的代码库,我已经很久没有添加新的 .cpp 文件了。我一直很焦急,直到读到这个,你的第 3 步,我才意识到我必须将 .o 文件添加到链接器命令中。

  • 很好的答案。我忘了将函数设置为 ClassName::function_name 了。修复此问题后,我解决了这个问题。

  • @JaMit 您能否用 zweack 示例更新您的答案(cpp 中缺少 ClassName::)?我遇到了完全相同的问题。这是您盯着问题却看不到它的情况之一。所以我认为值得直接提及它。

  • @Roman 我可能不明白你的要求。在我看来,你要求答案陈述“如果函数定义在类定义之外,则确保函数定义使用限定名称,如 ClassName::function_name。\”然而,答案已经说明了这一点。

  • 引用 16

    值得一提的是,忘记虚拟析构函数的主体会产生以下结果:

    对“CYourClass 的 vtable”的引用未定义。

    我添加了一条注释,因为错误消息具有误导性。(这是 gcc 版本 4.6.3。)

  • 我必须明确地将空虚拟析构函数的主体放入定义文件 (*.cc) 中。将其放在标头中仍然会出现错误。

  • 请注意,一旦我将虚拟析构函数添加到实现文件中,gcc 就会告诉我实际错误,即另一个函数缺少函数体。

  • @PopcornKing 我看到了同样的问题。即使在头文件中定义 ~Destructor = default; 也无济于事。有没有针对 gcc 提交的记录错误?

  • 这可能是一个不同的问题,但我的问题只是没有非虚拟析构函数的实现(切换到唯一/共享指针并将其从源文件中删除,但在标题中没有“实现”)

返回
作者最近主题: