Qt之美(二):元对象

本文的英文版在这里:http://xizhizhu.blogspot.com/2010/12/beauty-of-qt-2-meta-object.html

 

除了D指针,Qt中另一个很有意思的部分就是Q_OBJECT宏了。该宏提供了对元对象的访问,使得能够使用比如信号和槽等QObject的更多特性。元对象提供了诸如类名、属性和方法等的信息,也被称为“反射”。


通过使用QMetaObject,我们能够用如下代码显示一些类的信息:

[cpp]  view plain copy
  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++的性能和可扩展性

 

假设我们有如下所示的简单类:

[cpp]  view plain copy
  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会自动创建以下信息:

[cpp]  view plain copy
  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. };  
 

 

以上信息,及其基类的相关信息,都保存在该类对应的元对象中:

[cpp]  view plain copy
  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。该模板函数利用了元对象系统的信息,避免了在运行时进行类型转换:

[cpp]  view plain copy
  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. }  
 

 

这里,目标类型的元对象仅仅检查其是否从自身继承而来:

[cpp]  view plain copy
  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时,该函数会被自动调用:

[cpp]  view plain copy
  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函数被调用:

[cpp]  view plain copy
  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_castint(*)>(_a[1]))); break;  
  11.     case 1: mySlot((*reinterpret_castint(*)>(_a[1]))); break;  
  12.     default: ;  
  13.     }  
  14.     // 删除被该类“消耗”的ID,使得其子类类在处理时ID总是从0开始,而返回值-1则表示该函数已被调用  
  15.     _id -= 2;  
  16.   }  
  17.   return _id;  
  18. }  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值