Qt的元对象系统(meta-object system)提供信号与槽机制,可用于对象间通信、运行时类别信息(RTTI)和动态属性系统。
元对象系统基于三个方面:
QObject类:以它作为基类的对象才能实现元对象系统的特性。
Q_OBJECT宏: 在类的私有区声明这个宏可以打开诸如动态属性、信号与槽等元对象功能。
Meta-Object 编译器 (moc) 给每个QObject 的子类生成用以实现各种元对象功能的代码。
moc工具会扫描C++源文件,如果发现有包含Q_OBJECT宏的类声明,就生成另外一个包含这些类的元对象代码的C++源文件。生成的源文件要么在类源文件里用#include包含,或者(更常见)与类的实现代码直接进行编译连接。
除了提供信号与槽的对象间通讯机制(引入元对象系统的主要原因),元对象还提供以下功能:
QObject::metaObject() 返回类所属的meta-object(元对象)
QMetaObject::className() 在运行时以字符串形式返回类名,无需C++编译器提供运行时类别信息(RTTI)的支持。
QObject::inherits() 函数检查一个对象是否是QObject继承树内一个子类的实例。
QObject::tr() and QObject::trUtf8() 为国际化而翻译字符串。
QObject::setProperty() 和 QObject::property() 以名字方式动态地设置属性。
QMetaObject::newInstance() 构造类的新实例。
用qobject_cast()方法也可以对QObject的派生类对象进行动态类型转换。qobject_cast()方法跟标准C++的dynamic_cast()很像,但它不需要RTTI支持并且可以跨动态链接库转换。这个方法会尝试把参数转换成尖括号内的类型,如果对象有正确的类型(在运行时检查)它就返回一个非零的指针,如果对象类型不兼容时则返回0。
例如,假设MyWidget 是继承自QWidget 并且声明了宏Q_OBJECT:
QObject *obj = new MyWidget;
这个 obj变量的类型是QObject *,实际上指向一个MyWidget对象,所以我们可以正常的把它做类型转换:
QWidget *widget = qobject_cast<QWidget *>(obj);
因为这个obj对象实际上就是MyWidget,MyWidget是Qwidget的子类,因此从QObject 到 QWidget的类型转换是成功的。 既然我们知道obj是MyWidget类型的,我们也可以把它转换成 MyWidget *:
MyWidget *myWidget = qobject_cast<MyWidget *>(obj);
转换到MyWidget类型也是成功的,因为 qobject_cast() 并不区分内建的Qt类型和自定义类型。
QLabel *label = qobject_cast<QLabel *>(obj);
// 此时label 是 0
可是,转换到QLabel却是失败的。返回的指针为0。这使得我们可以在运行时根据对象的类型而做不同的操作:
if (QLabel *label = qobject_cast<QLabel *>(obj)) {
label->setText(tr("Ping"));
} else if (QPushButton *button = qobject_cast<QPushButton *>(obj)) {
button->setText(tr("Pong!"));
}
虽然在程序中我们可以仅用QObject而不用Q_OBJECT 宏(即不用任何元对象代码),但是信号与槽以及上面所说的其他功能就就都不能用了。从元对象系统的角度来看,一个不用元对象代码的QObject 子类是跟它们使用元对象代码的祖先类是等价的。比方说QMetaObject::className() 就不会返回你的类的名字,而是返回使用元对象代码的最近祖先类(ancestor class)的名字。
因此我们强烈建议不管用不用信号与槽和属性,所有QObject的派生类都应该使用Q_OBJECT宏。