作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
什么是 Qt 元对象系统
Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。
元对象系统由以下三个基础组成:
- QObject 类,是所有使用元对象系统的类的基类。换句话说只有继承 QObject 才能使用元对象系统;
- Q_OBJECT 宏,在一个类的 private 部分声明 ,使得类可以使用元对象的特性,如动态属性、信号与槽;
- MOC(元对象编译器),为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。构建项目时,MOC 工具读取 C++ 源文件,当它发现类的定义里有 Q_OBJECT 宏时,它就会为这个类生成另外一个包含有元对象支持代码的 C++ 源文件,这个生成的源文件连同类的实现文件一起被编译和连接。通常这个新的C++原文件会再以前的C++原文件前面加上moc_作为新的文件名;
元对象系统的功能
除了提供在对象间通讯的机制外,元对象系统还包含以下几种功能:
QObject::metaObject()
方法,获得与一个类相关联的meta-object;QMetaObject::className()
方法,在运行期间返回一个对象的类名,不需要本地C++编译器的 RTTI(run time type information)支持;QObject::inherits()
方法,用来判断一个对象的类是不是从一个特定的类继承而来;QObject::tr()、QObject::trUtf8()
方法,为软件的国际化翻译字符串;QObject::setProperty()、QObject::property()
方法,根据属性名动态的设置和获取属性值;QMetaObject::newInstance()
方法,构造类的新实例;- 使用
qobject_cast()
方法可以在在 QObject 类之间提供动态转换,qobject_cast()
方法的功能类似于标准 C++ 的 dynamic_cast(),但是 qobject_cast() 不需要RTTI的支持。在一个 QObject 类或者它的派生类中,如果不定义 Q_OBJECT 宏,那么这些功能将不能被使用。从 meta-object 的观点来看,一个没有定义 Q_OBJECT 宏的类与它最接近的那个祖先类是相同的。那么 QMetaObject::className() 方法返回的名字并不是该类的名字,而是与它最近接的那个祖先类的名字。所以,任何从 QObject 继承的类都必须定义 Q_OBJECT 宏。
Meta Object 的所有数据和方法都封装在 QMetaObject 类中,它包含一个 Qt 类的 meta 信息,并且提供查询功能。meta 信息包含:
- 信号表(signal table),与对应 Qt 类相关的系统定义及自定义的 signal 的名字;
- 槽表(slot table),与对应 Qt 类相关的系统定义及自定义的 slot 的名字;
- 类信息表(class info table),Qt 类的类型信息;
- 属性表(property table),与对应类中的所有属性的名字;
- 指向 parent meta object 的指针;
Q_OBJECT 宏的定义
Q_OBJECT 定义在 /src/corelib/kernel/Qobjectdefs.h 文件中,Q_OBJECT 宏的定义如下:
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
Q_OBJECT_GETSTATICMETAOBJECT \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \
Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
QMetaObject 类型的静态成员变量 staticMetaObject 是元数据的数据结构。metaObject,qt_metacast,qt_metacall、qt_static_metacall 四个虚函数由 MOC 在生成的 moc_xxx.cpp 文件中实现。
1)metaObject 的作用是得到元数据表指针;
2)qt_metacast 的作用是根据签名得到相关结构的指针,返回void*指针;
3)qt_metacall 的作用是查表然后调用调用相关的函数;
4)qt_static_metacall 的作用是调用元方法(信号和槽);
元对象编译器 MOC
- MOC 的功能
1)处理 Q_OBJECT 宏和 signals/slots 关键字,生成信号和槽的底层代码;
2)处理 Q_PROPERTY() 和 Q_ENUM() 生成 property 系统代码;
3)处理 Q_FLAGS() 和 Q_CLASSINFO() 生成额外的类 meta 信息;
4)不需要 MOC 处理的代码可以用预定义的宏括起来,如下:
#ifndef Q_MOC_RUN
…
#endif
-
MOC 的限制
1)模板类不能使用信号/槽机制;
2)MOC不扩展宏,所以信号和槽的定义不能使用宏, 包括 connect 的时候也不能用宏做信号和槽的名字以及参数;
3)从多个类派生时,QObject 派生类必须放在第一个。 QObject(或其子类)作为多重继承的父类之一时,需要把它放在第一个。 如果使用多重继承,MOC 在处理时假设首先继承的类是 QObject 的一个子类,需要确保首先继承的类是 QObject 或其子类;
4)函数指针不能作为信号或槽的参数, 因为其格式比较复杂,MOC 不能处理。可以用typedef 把它定义成简单的形式再使用;
5)用枚举类型或 typedef 的类型做信号和槽的参数时,必须 fully qualified。这个词中文不知道怎么翻译才合适,简单的说就是,如果是在类里定义的,必须把类的路径或者命名空间的路径都加上, 防止出现混淆。如 Qt::Alignment 之类的,前面的 Qt 就是 Alignment 的 qualifier;
6)信号和槽不能返回引用类型;
7)signals 和 slots 关键字区域只能放置信号和槽的定义,不能放其它的如变量、构造函数的定义等,友元声明不能位于信号或者槽声明区内;
8)嵌套类不能含有信号和槽 ; -
自定义类型的注册
Qt 线程间传递自定义类型数据时,自己定义的类型如果直接使用信号槽来传递的话会产生下面这种错误:
QObject::connect: Cannot queue arguments of type 'XXXXX' (Make sure 'XXXXX' is registed using qRegisterMetaType().)
错误原因:当一个 signal 被放到队列中(queued)时,参数(arguments)也会被一起放到队列中,参数在被传送到 slot 之前需要被拷贝、存储在队列中;为了能够在队列中存储参数,Qt 需要去 construct、destruct、copy 参数对象,而为了让 Qt 知道怎样去作这些事情,参数的类型需要使用 qRegisterMetaType 来注册;
注册自定义类的步骤如下:
1)自定义类型时在类的顶部包含:#include ;
2)在类型定义完成后,加入声明:Q_DECLARE_METATYPE(XXX);
3)在 main() 函数中注册自定义类类型:qRegisterMetaType(“XXX”),如果希望使用类型的引用,同样要注册:qRegisterMetaType(“XXX&”);