元对象是指用于描述另一个对象结构的对象。使用编程语言具体实现时,其实就是一个类的对象,只不过这个对象专门用于描述另一个对象而已,比如 class B{…}; class A{…B mb;…};假设 mb 是用来描述类 A 创建的对象的,则 mb 就是元对象。
一、元对象系统
Qt 的元对象系统提供的功能有:对象间通信的信号和槽机制、运行时类型信息和动态属性系统等。
元对象系统是 Qt 对原有的 C++进行的一些扩展,主要是为实现信号和槽机制而引入的,信号和槽机制是 Qt 的核心特征。
要使用元对象系统的功能,需要满足以下三个条件
该类必须继承自 QObject 类。
必须在类声明的私有区域添加 Q_OBJECT 宏,该宏用于启动元对象特性,然后便可使用动态特性、信号和槽等功能了。
元对象编译器(moc)为每个 QObject 的子类,提供实现了元对象特性所必须的代码。
元对象系统具体运行原则
因为元对象系统是对 C++的扩展,因此使用传统的编译器是不能直接编译启用了元对象系统的 Qt 程序的,对此在编译 Qt 程序之前,需要把扩展的语法去掉,该功能就是 moc 要做的事。
moc 全称是 Meta-Object Compiler(元对象编译器),它是一个工具(类似于 qmake),该工具读取并分析 C++源文件,若发现一个或多个包含了 Q_OBJECT 宏的类的声明,则会生成另外一个包含了 Q_OBJECT 宏实现代码的 C++源文件(该源文件通常名称为 moc_*.cpp) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译链接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件,而是与原文件一起编译。
二、Q_OBJECT
Q_OBJECT源码如下:
/* qmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
QT_WARNING_PUSH \
Q_OBJECT_NO_OVERRIDE_WARNING \
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: \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
struct QPrivateSignal {}; \
QT_ANNOTATE_CLASS(qt_qobject, "")
/* qmake ignore Q_OBJECT */
#define Q_OBJECT_FAKE Q_OBJECT QT_ANNOTATE_CLASS(qt_fake, "")
#ifndef QT_NO_META_MACROS
/* qmake ignore Q_GADGET */
#define Q_GADGET \
public: \
static const QMetaObject staticMetaObject; \
void qt_check_for_QGADGET_macro(); \
typedef void QtGadgetHelper; \
private: \
QT_WARNING_PUSH \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
QT_ANNOTATE_CLASS(qt_qgadget, "") \
/*end*/
Q_OBJECT宏可以告诉预编译器该类具有gui元素,并且需要通过“ moc”运行。
此时 moc 工具是通过 Qt Creator 来使用的,因此必须保证 moc 能发现并处理项目中包含有 Q_OBJECT 宏的类,为此,需要遵守以下规则
从 QObject 派生的含有 Q_OBJECT 宏的类的定义必须在头文件中。
确保 pro 文件中,是否列举了项目中的所有源文件(SOURCES 变量)和头文件(HEADERS 变量)
应在头文件中使用逻辑指令(比如#ifndef)防止头文件被包含多次。
QObject 类应是基类列表中的第一个类。
由以上规则可见,使用 Qt Creator 编写代码时,类应定义在头文件中,成员函数的定义应位于源文件中(这样可避免头文件被包含多次产生的重定义错误),虽然这样编写程序比较麻烦,但这是一种良好的代码组织方式。
不按规则 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 未定义。
若定义了QObject 类的派生类,并进行了构建,在这之后再添加 Q_OBJECT 宏,则此时必须执行一次 qmake 命令(“构建”>“执行 qmake”),否则 moc 不能生成代码。
三、反射机制
reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。
元对象系统提供的功能之一是为 QObject 派生类对象提供运行时的类型信息及数据成员的当前值等信息,也就是说,在运行阶段,程序可以获取 QObject 派生类对象所属类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是反射机制。
因为 Qt 的元对象系统必须从 QObject 继承,又从反射机制的主要作用可看到,Qt 的元对象系统主要是为程序提供了 QObject 类对象及其派生类对象的信息,也就是说不是从 QObject 派生的类对象,则无法使用 Qt 的元对象系统来获取这些信息。
3.1 Qt 具体实现反射机制的方法
Qt 使用了一系列的类来实现反射机制,这些类对对象的各个方面进行了描述,其中QMetaObject 类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系统的核心类,通过该类的成员函数可以获取 QObject 及其派生类对象的所有元信息,因此可以说 QMetaObject 类的对象是 Qt 中的元对象。注意:要调用 QMetaObject 类中的成员函数需要使用 QMetaObject 类型的对象。
对对象的成员进行描述:一个对象包含数据成员、函数成员、构造函数、枚举成员等成员,在 Qt 中,这些成员分别使用了不同的类对其进行描述,比如函数成员使用类QMetaMethod 进行描述,属性使用 QMetaProperty 类进行描述等,然后使用QMetaObject 类对整个类对象进行描述,比如要获取成员函数的函数名,其代码如下:
QMetaMethod qm = metaObject->method(1); //获取索引为 1 的成员函数
qDebug()<
3.2 使用 Qt 反射机制的条件
需要继承自 QObject 类,并需要在类之中加入 Q_OBJECT 宏。
注册成员函数:若希望普通成员函数能够被反射,需要在函数声明之前加入QObject::Q_INVOKABLE 宏。
注册成员变量:若希望成员变量能被反射,需要使用 Q_PROPERTY 宏。
3.3 Qt 反射机制实现原理简述
Q_OBJECT 宏展开之后有一个虚拟成员函数 meteObject(),该函数会返回一个指向QMetaObject 类型的指针,其原型为
virtual const QMetaObject *metaObject() const;
因为启动了元对象系统的类都包含 Q_OBJECT 宏,所以这些类都有含有 metaObject()虚拟成员函数,通过该函数返回的指针调用 QMetaObject 类中的成员函数,便可查询到 QObject 及其派生类对象的各种信息。
Qt 的 moc 会完成以下工作
为 Q_OBJECT 宏展开后所声明的成员函数的成生实现代码
识别 Qt 中特殊的关键字及宏,比如识别出 Q_PROPERTY 宏、Q_INVOKABLE宏、slot、signals 等
3.4 使用反射机制获取与类相关的信息
QMetaObject 类中获取与类相关的信息的成员函数有
const char* className() const;
获取类的名称,注意,若某个 QObject 的子类未启动元对象系统(即未使用 Q_OBJECT宏),则该函数将获取与该类最接近的启动了元对象系统的父类的名称,而不再返回该类的名称,因此建议所有的 QObject 子类都使用 Q_OBJECT 宏。
const QMetaObject* superClass() const;
返回父类的元对象,若没有这样的对象则返回 0。
bool inherits(const QMetaObject* mo) const;
若该类继承自 mo 描述的类型,则返回 true,否则返回 false。类被认为继承自身。
QObject 类中获取与类相关的信息的成员函数有
bool inherits(const char* className) const;
若该类是className 指定的类的子类则返回true,否则返回false。类被认为继承自身。
以下是一个示例:
//头文件 m.h 的内容
#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include
class A:public QObject{ Q_OBJECT};
class B:public A{ Q_OBJECT};
class C:public QObject{Q_OBJECT};
class D:public C{};
#endif // M_H
//源文件 m.cpp 的内容
#include "m.h"
#include
#include
using namespace std;
int main()
{
A ma; B mb; C mc; D md;
const QMetaObject *pa=ma.metaObject();
const QMetaObject *pb=mb.metaObject();
cout<className()<inherits(pa)<inherits(pb)<inherits(pa)<className()<