Q_OBJECT 导致 error: undefined reference to `vtable for XXXX' 错误

本文详细介绍了在Qt编程中使用Q_OBJECT宏及其与moc工具的关系。moc是Qt的元对象编译器,用于处理含有Q_OBJECT宏的C++类,生成元对象代码以支持信号槽等特性。文章还提供了如何正确配置makefile以确保moc正确运行的方法。

        在 Qt 编译环境下,自定义类继承 Qt 类时,程序报错 error: undefined reference to `vtable for XXXX'。这个错误看起来是虚函数表无法引用到,也就是继承 Qt 类失败了。

        解决方案:删除编译文件夹,重新 rebuild 工程

        出于对 Q_OBJECT 宏的好奇,大致读了下 Qt 帮助文档对 Q_OBJECT 宏的解释,具体如下(英语不太好,谅解):


Using the Meta-Object Compiler(moc)

元对象编译器(moc)它是用来处理 Qt's C++ 扩展程序。

moc 工具读取一个 C++ 头文件,如果它一个或多个在类声明中包含 Q_OBJECT 宏,它会为这些类产生一个包含元对象代码 C++ 源文件。除了其他方面,对于信号槽机制、运行时类型信息、动态属性系统,元对象代码是必须的。

由 moc 生成的 C++ 源文件必须被编译并与实现类相链接。

如果你使用 qmake 去创建你的 makefiles,在需要的时候编译规则将包括调用 moc,因此你不需要直接使用 moc。针对更多关于 moc 信息,请看 Why Does Qt Use Moc for Signals and Slots?


Usage

moc 典型的用法是和一个包含类声明的输入文件,就像这样:

class MyClass : public QObject
{
    Q_OBJECT

public:
    MyClass(QObject* parent = 0);
    ~MyClass();

signals:
    void mySignal();

public slots:
    void mySlot();
}

除上面所示的信号和槽之外, moc 还实现了对象属性就像下一个例子。Q_PROPERTY() 宏声明了一个对象属性,而Q_ENUMS() 宏声明一系列枚举类型在类里面,它对属性系统里面是有用的。

在下面的例子中,我们声明了一个枚举类型的属性 Priority ,另外调用 priority 有一个 get 函数 priority() 和一个设置函数 setPriority() 。

class MyClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority)
    Q_ENUMS(Priority)

public:
    enum Priority { High, Low, VeryHigh, VeryLow };

    MyClass(QObject *parent = 0);
    ~MyClass();

    void setPriority(Priority priority) { m_priority = priority; }
    Priority priority() const { return m_priority; }

private:
    Priority m_priority;
};

Q_FLAGS() 宏声明了一些枚举类型它可以作为标记去使用,例如,或操作的一起使用。另外一个宏,Q_CLASSINFO() ,允许你附加额外的 name/value 对到类的元对象:

class MyClass : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("Author", "Oscar Peterson")
    Q_CLASSINFO("Status", "Active")

public:
    MyClass(QObject *parent = 0);
    ~MyClass();
};

被 moc 生成的输出文件必须被编译和链接,就像你程序中其他的 C++ 代码,否则编译将会失败在最终的链接阶段。如果你使用 qmake ,那么这些动作都是自动的。当 qmake 运行时,它会解析项目头文件并生成规则去调用 moc 为这些包含 Q_OBJECT 宏的文件。

如果在文件 myclass.h 有发现类声明,moc 的输出应该会放在一个叫 moc_myclass.cpp 里。然后通常这个文件会被编译,产生的结果在一个 object 文件内,譬如,在 Window 系统上是 moc_myclass.obj。然后这个 object 文件会被包括到 object 列表文件内,在程序最终的编译阶段一起链接。

Writing Make  Rules for Invoking moc

除了最简单的测试程序,推荐你自动运行 moc 。通过添加一些规则到你程序的 makefile 里, make 可以在必要的时候处理运行中的 moc 并处理 moc 的输出。

针对在编译你的 makefiles 时,我们推荐你使用 qmake makefile 生成工具。这个工具生成了一个 makefile 以至于做了所有必要的 moc 处理。

如果你想你自己创建你的 makefiles,这里有些小窍门关于如何使用 moc 处理。

对于在头文件中类声明的 Q_OBJECT 宏,如果你仅仅只是使用 GNU 制作,这里有一个有用的 makefile 规则:

moc_%.cpp: %.h
        moc $(DEFINES) $(INCPATH) $< -o $@

如果你想更灵活的写,你可以使用按下面形式的个体规则:

moc_foo.cpp: foo.h
        moc $(DEFINES) $(INCPATH) $< -o $@

你必须而且记得去添加 moc_foo.cpp 到你的 SOURCES(替代你喜欢的名字)变量里,并且 moc_foo.o 或 moc_foo.obj 添加到你的 OBJECTS 变量里。

这两个例子假设 $(DEFINES) 和 $(INCPATH) 扩展到定义和包括路径选项并传递到 C++ 编译器。这些需求都会被 moc 在源文件里去预处理。

我们更喜欢命名我们的 C++ 源文件伟 .cpp,如果你喜欢你也可以使用其他的扩展,正如 .C, .cc, .CC, .cxx 和 .c++。

对于 Q_OBJECT 类声明在实现 (.cpp) 文件,我们建议一个 makefile 规则应该像这样:

foo.o: foo.moc

  foo.moc: foo.cpp
          moc $(DEFINES) $(INCPATH) -i $< -o $@

这可以保证在编译 foo.cpp 之前运行 moc,然后你可以输入:

#include "foo.moc"

在 foo.cpp 的结尾,在文件中声明的所有类会全部知道。

Command-Line Options

这里的命令行选项被 moc 所支持:

-o<file>: 写输出至<file>,而不是标准输出。

-f[<file>]: 强制生成一个 #include 语句在输出里。对于头文件的扩展默认是 H 或者 h。这个选项是有用的,如果你有头文件没有随标准命名转换。<file> 部分是可选的。

-i: 不要在输出中生成一个 #include 语句,这可能用于在包含一个或多个类声明的 C++ 文件上运行 moc。 然后你应该 #include 元对象代码在你的 .cpp 文件里。

-nw: 不要生成任何的警告(不推荐)。

-p<path>: 在生成的 #include 语句中,使 moc 预先设置 <path>/ 到文件名。

-I<dir>: 为头文件添加文件夹到包含路径。

-D<macro>[=<def>]: 定义宏,带有可选选项。

-U<macro>: 不要定义宏。

-M<key=value>: 附件额外的 meta 数据到插件程序,如果一个类有指定 Q_PLUGIN_METADATA,键值对将会添加到它的 meta 数据。插件运行时会在 json 对象得到解析后将会结束(从 QPluginLoader 访问)。这个参数典型的用法是标记静态插件和信息解析被编译系统。

@<file>: 读取额外的命令行选项从<file>,文件的每一行都会作为单一选项对待。空行会被忽略。注意,在选项文件它自己里面这个选项不被支持(即,一个选项文件不能“包含”另外一个文件)。

-h: 显示用法和选项列表。

-v: 显示 moc 版本号。

-Fdir: OS X。添加框架目录 dir 到要搜索的头文件的头目录列表。这些与那些通过 -I 选项指定目录交错,并且按从左到右的顺序扫描(看 gcc )。正常情况下,使用 -F/ Library/ Frameworks/

你可以明确的告诉编译器不要解析一个头文件 .moc 定义的预处理符号 Q_MOC_RUN 部分。所有的被包围的代码

#ifndef Q_MOC_RUN
    ...
#endif

会被 moc 跳过。

Diagnostics

moc 将会警告你关于一些危险或非法的构造在 Q_OBJECT 类型声明。

如果你的程序在最终的编译阶段,你得到了一些链接错误,说 YouClass::className() 没有定义或 YouClass 缺少一个 vtable,肯定是有些事做错了。通常来说,你有忘记去编译或者 #include moc 生成的 C++ 代码,或者(在前一种情况)包含对象文件在链接命令。如果你使用 qmake ,试着运行它去更新你的 makefile。这一点应该要做到。

Limitations

moc 不会处理所有的 C++,主要问题是类模板不能有信号和槽。这里有一个例子:

class SomeTemplate<int> : public QFrame
{
    Q_OBJECT
    ...

signals:
    void mySignal(int);
};

其次,下面的构造是非法的。它们都有替代方案我们认为通常更好,因此,消除这些限制对我们来说没有很高的优先级。

Mutiple Inheritance Requires QObject to Be First

如果你使用了多继承, moc 会假设第一个继承类是 QObject 的一个子类。并且,只确保仅第一个继承类是 QObject

// correct
class SomeClass : public QObject, public OtherClass
{
    ...
};

不支持使用 QObject 的虚继承。

Function Pointers Cannot Be Signal or Slot Parameters

在多数情况下你可能考虑使用函数指针作为信号或槽的参数,我们认为继承是更好的选择。这里有一个非法语法的例子:

class SomeClass : public QObject
{
    Q_OBJECT

public slots:
    void apply(void (*apply)(List *, void *), char *); // WRONG
};

你可以绕过这个限制,就像这样:

typedef void (*ApplyFunction)(List *, void *);

class SomeClass : public QObject
{
    Q_OBJECT

public slots:
    void apply(ApplyFunction, char *);
};

有时候使用继承和虚函数去替代函数指针可能更好。

Enums and Typedefs Must Be Fully Qualified for Signal and Slot Parameters

当检查这个参数的签名,QObject::connect()会逐个的比对数据类型。因此,Alignment 和 Qt::Alignment 会作为两个不同的类型对待。为了绕过这个约束,请确保当声明信号和槽,并且当建立连接时完全限定数据类型。例如:

  class MyClass : public QObject
  {
      Q_OBJECT

      enum Error {
          ConnectionRefused,
          RemoteHostClosed,
          UnknownError
      };

  signals:
      void stateChanged(MyClass::Error error);
  };

Nested Classes Cannot Have Signals or Slots

这里有一个违法构造的例子:

  class A
  {
  public:
      class B
      {
          Q_OBJECT

      public slots:   // WRONG
          void b();
      };
  };

Signal/Slot return types cannot be references

信号和槽可以有返回类型,但是信号和槽的返回值会作为返回 void 对待。

Only Signals and Slots May Appear in the signals and slots Sections of a Class

如果你试着放置另外一个结构在类的 signals 和 slots 节点内,而不是信号和槽里, moc 将会报错。

See also Meta-Object System, Signals and Slots, and Qt's Property System. 



D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'D:\Qt\ZT_Modbus\ZTCommTable\ZtModbusOta.cpp:-1: error: undefined reference to `vtable for OTAThread'
03-22
<think>嗯,用户遇到了QT项目中的“undefined reference to vtable for OTAThread”错误。我需要先回忆一下这个错误的常见原因。通常这种错误虚函数表相关,可能是在类中有虚函数但没有正确实现,或者析构函数的问题。 首先,用户提到的OTAThread类可能继承QObject或者QThread,并且包含了一些虚函数。根据引用[2]的内容,没有用virtual修饰析构函数可能导致这个问题。所以应该检查OTAThread的析构函数是否声明为virtual,并且是否有实现。如果析构函数只有声明而没有定义,链接时就会找不到符号,导致虚表不完整。 其次,用户可能没有在类声明中使用Q_OBJECT宏。这个宏对于Qt的信号槽机制是必须的,而且它也会处理一些元对象系统的代码生成。如果遗漏了这个宏,moc工具就不会生成相应的元对象代码,从而导致虚表错误。这时候需要清理项目,重新运行qmake和构建,确保moc处理正确。 另外,检查是否所有的虚函数都有实现。比如,如果OTAThread重写了QThread的run()方法,但忘记提供实现,也会导致类似的错误。需要确保每个虚函数都有对应的函数体,即使是空的实现。 引用[2]中还提到,进行qmake和重新构建的步骤。有时候构建系统可能会缓存旧的元对象信息,导致新添加的Q_OBJECT宏没有被处理。因此,建议用户先执行qmake,再清理并重新构建项目,确保所有步骤都正确执行。 还有可能的问题包括类定义在头文件中没有正确包含,或者链接时没有将生成的moc文件包含进去。需要确认OTAThread的头文件被正确包含在项目中,并且moc生成的cpp文件被编译和链接。 总结下来,可能的解决步骤是:检查Q_OBJECT宏是否存在,确保虚析构函数正确声明和定义,实现所有虚函数,执行qmake和重新构建。此外,注意代码中的拼写错误或者作用域问题,比如类名是否正确,命名空间是否恰当等。</think>### 问题分析 QT项目中出现`undefined reference to vtable for OTAThread`错误,通常与虚函数表(vtable)未正确生成有关。以下是逐步解决方案: --- ### 1. **确保类中声明了`Q_OBJECT`宏** - **原因**:若类继承自`QObject`或其子类(如`QThread`),且包含信号/槽或需要元对象系统支持,必须添加`Q_OBJECT`宏。缺少此宏会导致moc工具无法生成虚函数表。 - **解决方法**: ```cpp class OTAThread : public QThread { Q_OBJECT // 必须添加 public: // ... }; ``` --- ### 2. **检查虚析构函数是否实现** - **原因**:若类中声明了虚函数(包括析构函数),但未实现,会导致虚函数表不完整[^2]。 - **解决方法**: ```cpp // 头文件声明 class OTAThread : public QThread { Q_OBJECT public: explicit OTAThread(QObject *parent = nullptr); virtual ~OTAThread(); // 必须声明为virtual }; // 源文件实现 OTAThread::~OTAThread() { // 析构逻辑(可为空) } ``` --- ### 3. **执行qmake并重新构建** - **原因**:修改头文件后需重新生成moc文件,否则虚函数表未被更新[^2]。 - **步骤**: 1. 点击Qt Creator菜单栏的 **【构建】→【执行qmake】**。 2. 点击 **【构建】→【清理项目】**。 3. 重新构建项目。 --- ### 4. **检查虚函数是否全部实现** - **原因**:若声明了虚函数但未实现,例如`run()`方法,会导致链接错误。 - **示例**: ```cpp class OTAThread : public QThread { Q_OBJECT protected: virtual void run() override; // 必须实现 }; // 源文件实现 void OTAThread::run() { // 线程逻辑 } ``` --- ### 5. **验证头文件包含与继承关系** - **原因**:若类未正确继承`QObject`或头文件未包含,会导致元对象系统失效。 - **示例**: ```cpp #include <QThread> // 确保包含基类头文件 class OTAThread : public QThread { // 正确继承 // ... }; ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

l357630798

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值