Qt元对象系统
Qt的元对象系统提供了信号与槽机制,运行时的类型信息,以及动态属性系统。
使用元对象系统的三个条件:
- 只有继承自QObject的派生类才能享受元对象系统的好处。
- 在类声明时添加Q_OBJECT宏开启元对象系统的功能。
- 元对象编译器编译出moc_*.cpp, 帮助Object派生类实现必要的代码。
moc
工具读取C++源文件,如果发现有类包含Q_OBJECT宏,它就创建另一个C++源文件(moc_*.cpp),为每个类生成生成包含元对象实现的代码。生成的moc源文件通常被包含到类的源文件中,或者类的实现一同被编译和链接。
除了提供信号与槽机制以为,元对象系统还提供以下特性:
QObject::metaObject()
返回该类相关的元对象。QMetaObject::className()
返回运行时的类名,而无需编译器的RTTI(Run-Time Type Information)支持。【标准C++中通过 dynamic_cast和typeid 提供 RTTI机制。】QObject::inherits()
通过此函数判断某对象是否是QObject继承树上的实例。1
2
3
4QTimer *timer = new QTimer; // QTimer inherits QObject
timer->inherits("QTimer"); // returns true
timer->inherits("QObject"); // returns true
timer->inherits("QAbstractButton"); // returns falseQObject::tr()
提供i18n支持。QObject::setProperty()
和QObject::property()
提供动态的属性系统, 可通过名称获取和设置对象属性。QMetaObject::newInstance()
构建某个类的实例
除此之外还可以通过qobject_cast()
进行QObject对象之前的动态转换,表现得就像标准C++中的dynamic_cast
一样,但好处是你并不需要RTTI支持。【 C++虽然在后期定义了dynamic_cast和typeid两个关键字,但并没有说明如何实现这两个关键字。这就造成了不同的编译器的实现不同,更别说提供RTTI功能的库千差万别。由此导致的最大问题就是程序的可移植性差,项目之间无法完美兼容。】
例如,我们假定MyWidget
继承自QWidget
并且也声明了Q_OBJECT宏:
1 | QObject *obj = new MyWidget; |
类型为QObject *
的obj
变量,实际上指向一个MyWidget
对象,因此我们可以这样转换:
1 | QWidget *widget = qobject_cast<QWidget *>(obj); |
上面的例子中,QObject
转换成QWidget
成功了,因为这个对象实际是一个MyWidget
, 而MyWidget
是QWidget
的派生类。同样地,我们可以进行如下转换:
1 | MyWidget *myWidget = qobject_cast<MyWidget *>(obj); |
但是如果我们将它转换成QLabel
呢,当然,会失败。
1 | QLabel *label = qobject_cast<QLabel *>(obj); |
所以我们可以通过qobject_cast
来进行运行时的类型判断,例如:
1 | if (QLabel *label = qobject_cast<QLabel *>(obj)) { |
当然,我们可以在不用Q_OBJECT宏和元对象信息的情况下仍旧使用QObject
作为基类,但是像信号和槽以及其他这里描述的特性将无法使用。以及,QMetaObject::className()
不会返回真实类的名称。
因此,不管你用不用信号与槽、属性,我们都强烈推荐所有QObject的派生类都使用Q_OBJECT
宏。