Qt之美(二):元对象
https://blog.csdn.net/zhu_xz/article/details/6048610
本文的英文版在这里:http://xizhizhu.blogspot.com/2010/12/beauty-of-qt-2-meta-object.html
除了D指针,Qt中另一个很有意思的部分就是Q_OBJECT宏了。该宏提供了对元对象的访问,使得能够使用比如信号和槽等QObject的更多特性。元对象提供了诸如类名、属性和方法等的信息,也被称为“反射”。
通过使用QMetaObject,我们能够用如下代码显示一些类的信息:
1 QObject obj;
2 const QMetaObject *metaObj = obj.metaObject();
3 qDebug() << "class name: " << metaObj->className();
4 qDebug() << "class info count: " << metaObj->classInfoCount();
5 qDebug() << "methods: ";
6 // 从QMetaObject::methodOffset()开始打印,使其不会显示父类的方法
7 for (int i = metaObj->methodOffset(); i < metaObj->methodCount(); ++i)
8 qDebug() << metaObj->method(i).methodType() << " " << metaObj->method(i).signature();
由于C++并没有提供对这些信息的任何支持,Qt引入了元对象编译器(moc)来完成相应的工作。moc会读取每个头文件,如果发现其中定义的类是继承自QObject,且定义了Q_OBJECT宏,便会创建一个相应的C++源代码文件(moc_*.cpp),来完成这些工作。通过代码生成的工作,Qt不仅能够获得诸如Java等语言的灵活性,还能很好的保证继承自C++的性能和可扩展性。
假设我们有如下所示的简单类:
1 class MyObject : public QObject
2 {
3 Q_OBJECT
4 public:
5 explicit MyObject(QObject *parent = 0);
6 void myFunc();
7 public slots:
8 void mySlot(int myParam);
9 signals:
10 void mySignal(int myParam);
11 };
moc会自动创建以下信息:
1 // 保存在QMetaObject::d.data指向的空间,其起始部分是一个QMetaObjectPrivate结构体
2 static const uint qt_meta_data_MyObject[] = {
3 5, // 版本号,其内部结构在Qt开发中有所改变
4 0, // 类名,其值为字符串qt_meta_stringdata_MyObject的偏移量
5 // 以下值为(数量,索引)对
6 0, 0, // 类信息
7 2, 14, // 这里定义了两个方法,其起始索引为14(即signal部分)
8 0, 0, // 属性
9 0, 0, // 枚举
10 0, 0, // 构造函数
11 0, // 标识
12 1, // signal数量
13 // 对于signal、slot和property,其signature和parameters为字符串qt_meta_stringdata_MyObject的偏移量
14 // signals: signature, parameters, type, tag, flags
15 18, 10, 9, 9, 0x05,
16 // slots: signature, parameters, type, tag, flags
17 32, 10, 9, 9, 0x0a,
18 0 // eod
19 };
20 // 保存在QMetaObject::d.stringdata指向的空间
21 static const char qt_meta_stringdata_MyObject[] = {
22 "MyObject/0/0myParam/0mySignal(int)/0"
23 "mySlot(int)/0"
24 };
以上信息,及其基类的相关信息,都保存在该类对应的元对象中:
1 const QMetaObject MyObject::staticMetaObject = {
2 { &QObject::staticMetaObject, // 指向其基类的元对象,保存在QMetaObject::d.superdata
3 qt_meta_stringdata_MyObject, qt_meta_data_MyObject, 0 }
4 };
这样,如果我们希望对QObject的对象进行类型转换,就不需使用开销较大的运算符dynamic_cast, 而能够直接使用qobject_cast。该模板函数利用了元对象系统的信息,避免了在运行时进行类型转换:
1 template <class T> inline T qobject_cast(QObject *object)
2 {
3 #if !defined(QT_NO_QOBJECT_CHECK)
4 reinterpret_cast(0)->qt_check_for_QOBJECT_macro(*reinterpret_cast(object));
5 #endif
6 return static_cast(reinterpret_cast(0)->staticMetaObject.cast(object));
7 }
这里,目标类型的元对象仅仅检查其是否从自身继承而来:
1 const QObject *QMetaObject::cast(const QObject *obj) const
2 {
3 if (obj) {
4 const QMetaObject *m = obj->metaObject();
5 do {
6 if (m == this)
7 return obj;
8 } while ((m = m->d.superdata));
9 }
10 return 0;
11 }
此外,moc会为每一个信号创建相应函数。当信号被emit时,该函数会被自动调用:
1 void MyObject::mySignal(int _t1)
2 {
3 void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
4 // 检查链接到该信号的所有slot,并根据链接类型进行调用
5 QMetaObject::activate(this, &staticMetaObject, 0, _a);
6 }
最后,这些信号都会通过moc创建的qt_metacall函数被调用:
1 int MyObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
2 {
3 // 如果该函数已被基类调用,则直接返回
4 _id = QObject::qt_metacall(_c, _id, _a);
5 if (_id < 0)
6 return _id;
7 // 根据函数的ID进行调用
8 if (_c == QMetaObject::InvokeMetaMethod) {
9 switch (_id) {
10 case 0: mySignal((*reinterpret_cast< int(*)>(_a[1]))); break;
11 case 1: mySlot((*reinterpret_cast< int(*)>(_a[1]))); break;
12 default: ;
13 }
14 // 删除被该类“消耗”的ID,使得其子类类在处理时ID总是从0开始,而返回值-1则表示该函数已被调用
15 _id -= 2;
16 }
17 return _id;
18 }