元对象系统
Qt的元对象系统为对象间通信、运行时类型信息和动态属性系统提供了信号和插槽机制。
元对象系统主要基于以下三种组成:
- QObject类为可以利用元对象系统的对象提供基类。
- 类声明私有部分中的Q_OBJECT宏用于启用元对象特性,如动态属性、信号和插槽。
- Meta-Object Compiler元对象编译器(moc)为每个QObject子类提供实现元对象特性所需的代码。
moc工具读取c++源文件。如果它发现一个或多个类声明包含Q_OBJECT宏,它会生成另一个c++源文件,该文件包含每个类的元对象代码。这个生成的源文件要么被#include到类的源文件中,要么(通常情况下)编译并链接到类的实现中。
除了为对象之间的通信提供信号和槽机制(引入该系统的主要原因)之外,元对象代码还提供了以下附加特性:
- QObject::metaObject()返回类的关联元对象。
- QMetaObject::className()在运行时以字符串形式返回类名,不需要通过c++编译器提供本机运行时类型信息(RTTI)支持。
- QObject:: inherited()函数返回一个对象是否是继承QObject继承树中指定类的类的实例。
- QObject::tr()和QObject::trUtf8()为国际化翻译字符串。
- QObject::setProperty()和QObject::property()根据名称动态设置和获取属性。
- QMetaObject::newInstance()构造一个类的新实例。
也可以在QObject类上使用qobject_cast()执行动态强制转换。qobject_cast()函数的行为类似于标准的c++ dynamic_cast(),优点是它不需要RTTI支持,可以跨动态库边界工作。它尝试将参数强制转换为尖括号中指定的指针类型,如果对象的类型正确(在运行时确定),则返回非零指针,如果对象的类型不兼容则返回nullptr。
例如,假设MyWidget继承自QWidget,并使用Q_OBJECT宏声明:
QObject *obj = new MyWidget;
类型为QObject *的obj变量实际上引用了一个MyWidget对象,因此我们可以适当地对其进行强制转换:
QWidget *widget = qobject_cast<QWidget *>(obj);
从QObject转换到QWidget是成功的,因为对象实际上是一个MyWidget,它是QWidget的子类。因为obj是一个MyWidget,所以可以将它强制转换为MyWidget *:
MyWidget *myWidget = qobject_cast<MyWidget *>(obj);
因为qobject_cast()不区分内置Qt类型和自定义类型,上述代码可以强制转换为MyWidget。
QLabel *label = qobject_cast<QLabel *>(obj);
// label is 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!"));
}
虽然可以在不使用Q_OBJECT宏和元对象代码的情况下使用QObject作为基类,但如果不使用Q_OBJECT宏,则信号和槽以及这里描述的其他特性都将不可用。从元对象系统的角度来看,一个没有元代码的QObject子类等价于它最近的带有元对象代码的原型类。这意味着,QMetaObject::className()不会返回该类的实际名称,而是其原型类的类名。
建议QObject的所有子类都使用Q_OBJECT宏,无论实际是否使用信号、槽和属性。