Qt链接错误:"undefined reference to vtable"

71
这是我的标题:
#ifndef BARELYSOCKET_H
#define BARELYSOCKET_H

#include <QObject>
//! The First Draw of the BarelySocket!

class BarelySocket: public QObject
{
    Q_OBJECT

public:
    BarelySocket();
public slots:
    void sendMessage(Message aMessage);
signals:
    void reciveMessage(Message aMessage);

private:
    //   QVector<Message> reciveMessages;
};

#endif // BARELYSOCKET_H

这是我的类:

#include <QTGui>
#include <QObject>
#include "type.h"
#include "client.h"
#include "server.h"

#include "barelysocket.h"

BarelySocket::BarelySocket()
{
    //this->reciveMessages.clear();
    qDebug("BarelySocket::BarelySocket()");
}

void BarelySocket::sendMessage(Message aMessage)
{
}

void BarelySocket::reciveMessage(Message aMessage)
{
}

我遇到了连接器错误:

undefined reference to 'vtable for BarelySocket'
  • 这意味着我有一个未实现的虚方法,但是我的类中没有虚方法。
  • 我注释掉了向量,以为它是原因,但错误并没有消失。
  • Message 是一个复杂的 struct,但是即使使用 int 也无法解决问题。

7
你尝试过从运行 qmake 开始进行彻底的构建吗?如果您的类的头文件由于某些原因未被 moc 处理,则可能会出现这种情况。 - Tyler McHenry
我正在使用QT Creator。我将所有cpp文件复制到一个新的干净项目中。 我删除了我错误编码的Slot实现。 然后问题就解决了。 感谢您的帮助! - Thomas
9个回答

161
任何时候添加新的Q_OBJECT宏调用,都需要再次运行qmake。你所提到的vtables问题与此直接相关。只需运行qmake,假设您的代码中没有其他问题,您就可以开始了。

34
在QT Creator中,从“Build”菜单中选择“run qmake”来运行qmake。 - Michael Hilbert
2
谢谢!从命令行来看,通常只使用make也会更新一些与qmake相关的内容,但显然不够。确实需要明确地运行qmake - Thomas
仅仅运行qmake并不足够,我必须删除我的构建文件夹然后重新编译。 - Paul Wintz
它可以工作,我只是重新运行了qmake。 - yasriady
对我没用。我尝试了所有的方法,但都没有成功。当我删除Q_OBJECT宏时,可以成功构建,但我认为这不是正确的方法! - Majid

53

我看到了很多解决问题的方法,但没有对其原因进行解释,所以在这里解释一下。

当编译器看到一个带有虚函数(直接声明或继承)的类时,它必须为该类生成一个虚函数表。由于类通常在头文件中定义(因此出现在多个翻译单元中),问题在于在哪里放置虚函数表。

一般来说,可以通过在每个定义了该类的TU*中生成虚函数表,然后让链接器消除重复来解决该问题。由于ODR**要求每次出现的类定义都必须相同,因此这是安全的。然而,这也会减慢编译速度,增加目标文件的大小,并需要链接器做更多的工作。

因此,作为优化,编译器将在可能的情况下选择特定的TU来放置虚函数表。在通用的C++ ABI***中,这个TU是实现类的关键函数的TU,关键函数是指在类中声明但未定义的第一个虚拟成员函数。

在Qt类的情况下,它们通常以Q_OBJECT宏开始,这个宏包含了声明。

virtual const QMetaObject *metaObject() const;

在宏中,由于它是第一个虚函数,通常是类的第一个虚函数,因此是类的关键函数。因此编译器在大多数TU中不会发出vtable,只会实现metaObject的那个TU。当处理头文件时,moc会自动写入该函数的实现。因此,您需要让moc处理头文件以生成新的.cpp文件,然后将.cpp文件包含在编译中。

因此,当您有一个定义了QObject衍生类的新头文件时,需要重新运行qmake,以便更新makefile以在新头文件上运行moc并编译生成的.cpp文件。

* TU:翻译为"翻译单元"。在C和C++中是一种术语,它指的是单个源文件加上从中传递包含的所有头文件。基本上就是编译器在处理单个源文件时所看到的内容。

** ODR:翻译为"一次定义规则"。C++标准中定义的一组规则,用于定义在不同的翻译单元中多次定义事物(函数、类等)时会发生什么。

*** ABI:翻译为"应用程序二进制接口"。描述编译后代码的组织方式,用于将目标文件链接在一起。Common C++ ABI是规范,Linux编译器通常遵循该规范以便它们可以相互操作。


7
很棒的写作。我正在使用CMake而不是qmake,我经常遇到这个错误,理解基本的C++虚函数表信息却依然无法找出问题所在,非常令人沮丧。谢谢! - Kyle Strand
@Kyle Strand 不应该只需要 set(CMAKE_AUTOMOC ON) 吗?我有一个 Qt 库,每次都必须运行 qmake 才能避免 vtable 错误。这是否意味着 cmake 没有按照应有的方式运行 moc - James Hirschorn
1
@JamesHirschorn 你可能认为这应该足够了,是的。实际上,似乎 moc 在每次构建时都会重新运行,而不考虑输入文件是否实际发生了更改。我不太确定 CMake 的根本问题是什么。 - Kyle Strand
@AlastairG 这听起来像是一个单独的问题。 - Sebastian Redl
1
@xcski 好主意,添加了一些定义。 - Sebastian Redl
显示剩余5条评论

18
我在创建一个小型的"main.cpp"文件来测试一些东西时,遇到了这个错误。
苦思冥想了一个小时左右,最终我将这个类从main.cpp中分离出来,放到了一个独立的hpp文件中,更新了.pro(项目)文件,然后该项目就能够完美地构建了。虽然这可能不是问题所在,但我认为这是有用的信息。

今天我犯了完全相同的错误!还浪费了大约一个小时左右... - R Kiselev
任何想法为什么会发生这种情况?今天我也遇到了这个问题。 - Harish Ganesan

11

从经验来看:通常执行 qmake && make clean && make 会有帮助。

我个人认为,有时候可能是因为更改检测/缓存效果或其他我不知道的原因引起的。我不知道为什么,但当我遇到这种错误时,这是我做的第一件事。

顺便说一下,在“recive”中有一个错别字。

你在构造函数中忘记调用 QObject 构造函数(在初始化列表中)。 (虽然它不能解决错误)


5
特别是当文件在运行 qmake 时并没有任何 Q_OBJECT 引用时,这将会非常有用。qmake 会认为不需要运行 moc,然后你会遇到虚表错误。make clean 并不总是必要的,但在进行某些结构性更改时需要使用它。 - Kaleb Pederson
5
请确保在您的pro文件的HEADERS部分中包含barelysocket.h。 - chalup
@chalup 这就是我的问题,当我使用hpp文件时!谢谢! - Hitokage

6

我在构建日志中发现 moc 没有被调用。尝试了清除所有内容,但没有效果。所以我删除了 .pro.user 文件,重新启动了 IDE,问题得到解决。


这正是我遇到的问题。当我添加一个非QObject父类的类时,触发了QtCreator的一个bug,但后来我手动修复了它。我运行了grep Class Project.user命令,结果发现文件丢失了。所以我只需删除.user文件,重新构建一切,错误就消失了。 - Nulik

3

信号不能有任何实现(这将由Qt生成)。从您的.cpp文件中删除reciveMessage实现。这可能会解决您的问题。

我看到的另一件事:由于BarelySocket类继承自QObject,因此必须拥有虚析构函数以避免在销毁过程中出现问题。对于所有继承自其他类的类都必须这样做。


删除receiveMessage的实现是必要的,但这将导致“多次定义符号”错误。如果基类(在本例中为QObject)具有虚析构函数,则所有派生类中的析构函数都会自动成为虚函数。因此,在这里不是问题。 - chalup

2

当你从QObject派生一个类(并使用Q_OBJECT宏),不要忘记明确定义和创建构造函数和析构函数类。仅使用编译器默认的构造函数/析构函数是不够的。清理/运行qmake(并清除moc_文件)的建议仍然适用。这解决了我的类似问题。


1
我曾经花费数小时来解决这个错误。通过将 .cpp 和 .h 文件放在一个单独的文件夹中 (!!),我最终解决了它。 然后在 .pro 文件中添加了该文件夹: INCLUDEPATH += $${_PRO_FILE_PWD_}/../MyClasses/CMyClassWidget
接着添加了 .cpp 和 .h 文件。 最终终于运行成功了。

0

我发现另一个可能导致这种情况的原因 - 因为qmake会解析您的类文件,如果您以非标准方式修改了它们,则可能会出现此错误。在我的情况下,我有一个自定义对话框,继承自QDialog,但我只想在构建Linux时编译和运行它,而不是Windows或OSX。我只需使用#ifdef __linux__将该类排除,使其不进行编译,在Linux中即使定义了__linux__,它也会干扰qmake


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接