简述
Qt的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类别信息和动态属性系统。
元对象系统由以下三个基础组成
- QObject类:所有使用元对象系统的类的基础
- Q_OBJECT宏:在一个类的private部分声明,它使得类可以使用元对象的特性,如动态属性、信号与槽
- MOC(元对象编译器):为每个QObject的子类提供必要的代码来实现元对象系统的特性
构建项目时,MOC工具读取C++源文件,当它发现类的定义里有Q_OBJECT 宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。
详细描述
除了为对象间的通信提供信号与槽(引入元对象系统的主要原因)机制外,元对象还提供以下特性:
QObject::metaObject()
返回类关联的meta-object对象QMetaObject::className()
在运行时以字符串的形式返回类名,无需C++编译器提供运行时类别信息(RTTI)的支持QObject::inherits(const char *className)
返回一个对象是否是QObject继承树上一个类的实例QObject::tr()
和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;
QObject *
类型的变量obj
实际上指向一个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!"));
}
尽管可以在不用Q_OBJECT宏(即:不用任何元对象代码)的情况下仍旧使用QObject作为基类,但是像信号和槽以及上面所说的其它特性都将无法使用。从元对象系统的角度来看,一个没有元对象代码的QObject子类等同于它最接近的有元对象代码的祖先。这意味着,QMetaObject::className()将不会返回你的类的真实名称,而是返回其祖先的名字。
因此,强烈建议所有QObject的子类都使用Q_OBJECT宏,不管实际上是否使用信号和槽,以及属性。