Qt笔记 元对象系统
元对象系统
一、元对象系统基本概念
1、Qt 的元对象系统提供的功能有:对象间通信的信号和槽机制、运行时类型信息和动态属性系统等。
2、元对象系统是 Qt 对原有的 C++进行的一些扩展,主要是为实现信号和槽机制而引入的, 信号和槽机制是 Qt 的核心特征。
3、要使用元对象系统的功能,需要满足以下三个条件
①、该类必须继承自 QObject 类。
②、必须在类声明的私有区域添加 Q_OBJECT 宏,该宏用于启动元对象特性,然后便可使用动态特性、信号和槽等功能了。
③、元对象编译器(moc)为每个 QObject 的子类,提供实现了元对象特性所必须的代码。
4、元对象系统具体运行原则
①、因为元对象系统是对 C++的扩展,因此使用传统的编译器是不能直接编译启用了元对象系统的 Qt 程序的,对此在编译 Qt 程序之前,需要把扩展的语法去掉,该功能就是moc 要做的事。
②、moc 全称是 Meta-Object Compiler(元对象编译器),它是一个工具(类似于 qmake), 该工具读取并分析 C++源文件,若发现一个或多个包含了 Q_OBJECT 宏的类的声明, 则会生成另外一个包含了 Q_OBJECT 宏实现代码的 C++源文件(该源文件通常名称为moc_*.cpp
) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译键接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件, 而是与原文件一起编译。
5、其他概念
①、元对象代码:指的是 moc 工具生成的源文件的代码,其中包含有 Q_OBJECT 宏的实现代码
②、moc 工具的路径为:F:\app\Qt5.8.0MinGw\5.8\mingw53_32\bin
二、Q_OBJECT 宏
列出该宏代码的目的主要是为了讲解使用 Qt Creator 时易犯的错误。
#define Q_OBJECT \
public: \
……
static const QMetaObject staticMetaObject; \ ❶
virtual const QMetaObject *metaObject() const; \ ❷
virtual void *qt_metacast(const char *); \ ❸
virtual int qt_metacall(QMetaObject::Call, int, void **); \ ❹
QT_TR_FUNCTIONS \ ❺
private: \
static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
……
1、选中 Q_OBJECT 然后按下 F2 可追踪到该宏的如上定义(仅列出关键代码)
2、可见,Q_OBJECT 宏为声明的类之中增加了一些成员,而且❷、❸、❹是虚函数成员(注意,这些虚函数没有定义),按 C++语法,虚函数必须定义或者被声明为纯虚函数,moc 工具的工作之一就是生成以上成员的定义,并且还会生成一些其他必要的代码。此处讲解 Q_OBJECT 宏代码的目的主要是为了讲解使用 Qt Creator 时易犯的错误及怎样正确使用 Q_OBJECT 宏,读者可查看由 moc 生成的 moc_*.cpp
的内容以了解更详细的信息。
三、使用Qt Creator启动元对象系统
1、此时 moc 工具是通过 Qt Creator 来使用的,因此必须保证 moc 能发现并处理项目中包含有 Q_OBJECT 宏的类,为此,需要遵守以下规则
- 从QObject 派生的含有 Q_OBJECT 宏的类的定义必须在头文件中。
- 确保 pro 文件中,是否列举了项目中的所有源文件(SOURCES 变量)和头文件(HEADERS 变量)
- 应在头文件中使用逻辑指令(比如#ifndef)防止头文件被包含多次。
- QObject 类应是基类列表中的第一个类。
- 由以上规则可见,使用 Qt Creator 编写代码时,类应定义在头文件中,成员函数的定义应位于源文件中(这样可避免头文件被包含多次产生的重定义错误),虽然这样编写程序比较麻烦,但这是一种良好的代码组织方式。
2、不按规则 1 的方法编写程序,则 moc 工具就不能正确生成代码,这时的错误原因通常是未定义由 Q_OBJECT 展开后在类中声明的虚函数引起的,其错误信息如下:
- 若使用 MinGw 编译,产生的错误信息类似如下:
undefined reference to 'vtable for A'
.表示类 A 的虚函数表(vtable)不能正常生成,通常是有虚函数未定义。 - 若使用 VC++2015 编译,产生的错误信息类似如下:LNK2001: 无法解析的外部符号
"public: virtual struct QMetaObject const * thiscall A::metaObject(void)const " (……)
.表示虚函数 A::metaObject 未定义。
3、以下错误不易被发现:若定义了 QObject 类的派生类,并进行了构建,在这之后再添加 Q_OBJECT 宏,则此时必须执行一次 qmake 命令(“构建”>“执行 qmake”),否则 moc 不能生成代码。
示例:使用 Qt Creator 启动元对象系统
新建空项目、添加一个 m.h 和 m.cpp 文件。
/*m.h 文件内容。要在 Qt Creator 中启动元对象系统,包含 Q_OBJECT 宏的类的定义必须位于头文件中,否则 moc 工具不能生成元对象代码*/
#ifndef M_H //用于防止头文件被多次包含的逻辑指令#define M_H
#include <QObject> //因为要使用 QObject 类,为此需要包含此头文件class B{};
//错误,moc 不能启动。因为多重继承时 QObject 必须位于基类列表中的第一位。
//class A:public B,public QObject{ Q_OBJECT };
class B
{
};
class A : public QObject, public B
{ /*要使用元对象系统必须继承自 QObject 类,且 QObject 应位于基类继承列表中的第一位。*/
Q_OBJECT //启动元对象系统。Q_OBJECT 必须位于私有区域。
public : A()
{
qDebug("DDDD\n");
}
}; //qDebug 函数用于在控制台输出一些信息
#endif // M_H
//m.cpp 文件内容。
#include "m.h"
int main(int argc, char *argv[])
{
A ma;
return 0;
}
运行以上程序,可在 debug 目录下找到一个 moc_m.cpp 的源文件,该源文件就是使用 moc 工具生成的,该源文件中的代码就是元对象代码,读者可查看其代码。若在该目录没有 moc_m.cpp 文件,说明 moc 工具未能正常启动,这时需在 Qt Creator 中执行 qmake 命令, 再构建程序。
四、在命令行启动元对象系统
1、在命令行需使用 moc 工具,并在源文件中包含 moc 工具生成的 cpp 文件。
2、此时包含 Q_OBJECT 的类不需要位于头文件中,假设位于 m.cpp 文件内,内容为:
#include <QObject>
class A : public QObject
{
Q_OBJECT
public:
A() {}
};
int main(int argc, char *argv[])
{
A ma;
return 0;
}
3、打开 Qt 5.8 for Desktop (MinGW 5.3.0 32 bit)命令行工具,输入如下命令moc d:\qt\m.cpp -o d:\qt\mm.cpp
。以上命令会根据 m.cpp 生成 mm.cpp 文件,mm.cpp 文件中的代码就是元对象代码,此处 m.cpp 和mm.cpp 都位于 d:\qt 文件夹下。
4、然后再次打开 m.cpp,在其中使用#include 把 mm.cpp 包含进去,如下
#include <QObject>
// #include "mm.cpp" //不能把 mm.cpp 包含在类 A 的定义之前,原因见下面的注释
class A : public QObject
{
Q_OBJECT
public:
A() {}
};
#include "mm.cpp" /*必须把 mm.cpp 包含在类A 的定义之后,因为 mm.cpp 源文件中有对类A 的成员的定义,此时必须见到类A 的完整定义。*/
int main(int argc, char *argv[])
{
A ma;
return 0;
}
5、然后再使用 qmake 生成项目文件和 makefile 文件,再使用 mingw32-make 命令即可。