• 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 }
    复制代码
  • 相关阅读:
    ingress高可用--使用DaemonSet方式部署ingress-nginx
    flask-Migrate模块
    flask 框架 前端和后端请求超时问题
    linux crontab执行python脚本问题
    python 装饰器
    Python 中实现装饰器时使用 @functools.wraps 的理由
    jquery ui dialog 中使用select2 导致select2的input失去焦点的解决方法
    Django之ModelForm组件
    Django之Model操作
    Java环境变量,真的还有必要配吗?
  • 原文地址:https://www.cnblogs.com/zhoug2020/p/14330571.html
Copyright © 2020-2023  润新知