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

        在 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. 



### QT中`undefined reference to vtable for Xian`错误的解决方案 当在QT开发过程中遇到`undefined reference to vtable for Xian`这样的错误时,这通常表明存在未正确处理的虚函数表(vtable)。以下是可能的原因及其对应的解决办法: #### 1. **Q_OBJECT宏的存在** 如果类`Xian`继承自`QObject`并包含了`Q_OBJECT`宏,则需要确保MOC(Meta Object Compiler)能够正确运行。这是因为`Q_OBJECT`宏引入了元对象机制,而此机制依赖于额外的编译步骤来生成必要的代码。 为了修复这一问题,可以尝试以下操作: - 执行`qmake`命令以重新生成Makefile,并触发MOC对含`Q_OBJECT`宏的头文件进行预处理[^2]。 ```bash qmake && make ``` #### 2. **析构函数是否为虚拟** 对于任何派生自`QObject`或其他基类的类来说,其析构函数应当被声明为`virtual`。这是为了避免潜在的对象销毁异常行为以及防止类似的链接错误发生。 例如,在定义`Xian`类时应如下所示: ```cpp class Xian : public QObject { Q_OBJECT public: virtual ~Xian(); // 确保析构函数是virtual类型的 }; ``` 注意这里显式地将析构函数标记成`virtual`的重要性[^5]。 #### 3. **检查`.pro`配置文件** 确认新的源码文件已被加入到项目的`.pro`文件之中。如果没有这样做的话,那么即使完成了其他所有的设置也可能仍然遭遇同样的连接期错误。 添加方式很简单,只需打开相应的`.pro`文件并将缺失的部分补充进去即可。比如假设新增了一个名为`xian.cpp`的实现文档,则应该像这样更新项目描述符: ```plaintext SOURCES += \ ... \ xian.cpp HEADERS += \ ... \ xian.h ``` 之后再次调用`qmake`刷新整个构建环境。 #### 4. **验证所有纯虚函数均已提供具体实现** 尽管已经提到过一次关于虚函数的话题,但还是有必要单独强调一点——即每一个声明出来的抽象成员都需要有实际的内容填充上去才行。否则即便解决了前面提及的各种状况也还是会碰到相同的难题。 举个例子说吧,假如我们的`Xian`里面有一个叫做`doSomething()`的方法而且它是纯粹形式上的东西(也就是只有原型却没有实体),那我们就得给它配上合适的逻辑表达出来才好继续往下走哦! 就像这样子啦: ```cpp void Xian::doSomething(){ qDebug()<<"Doing something!"; } ``` 当然咯,具体情况还得看业务需求哈~ 不一定非要打印这么一句简单的消息呢😊[^4]. 综上所述,针对此类问题可以从以上几个方面逐一排查直至找到根本原因为止。希望这些信息能帮您顺利解决问题!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

l357630798

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

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

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

打赏作者

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

抵扣说明:

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

余额充值