Hello,从今天开始将陆续给大家分享些Qt的核心内容,首先分享的就是元对象系统。
要说元对象系统,首先它支持的就是运行时类型识别,本篇作为一个引子,意在帮大家逐步的打开Qt如何支持运行时类型识别的大门。
在项目中,我们经常会听到动态类型识别,所谓动态类型识别就是指在程序运行的过程中辨别对象是否属于特定类的技术(Runtime TypeInfomation, RTTI)。
有时候,在程序运行过程中,函数需要识别其参数类型,这时动态类型识别就变得非常有用了。
那么我们就来自制一个简单动态类型识别系统。
首先第一个问题,我们应如何识别对象是否属于某个类呢?想要区别类,必须要给类设置一个惟一的标识。
因为类的静态成员不属于对象的一部分,而是类的一部分,也就是说在定义类的时候,编译器已经为这个类的静态成员分配内存了,不管实例化多少个此类的对象,类的静态成员在内存中都只有一份。因此,可以给每个类设置一个静态成员变量,此成员变量的内存地址就是这个类的标识!
例如:
#include <iostream> using namespace std; //学生类 class Student { public: const int* runTimeObject() { return &objectStudent; } static const int objectStudent; // objectStudent 成员的内存地址是类的唯一标识 }; const int Student::objectStudent = 1; // 随便初始化一个值就行了 //教师类 class Teacher { public: const int* runTimeObject() { return &objectTeacher; } static const int objectTeacher; // objectTeacher 成员的内存地址是类的唯一标识 }; const int Teacher::objectTeacher = 1; int main() { Student student; if(student.runTimeObject() == &Teacher::objectTeacher) // 用静态成员的地址辨别 student 对象是否属于 CGirl 类 { cout << "this a teacher \n"; } else if(student.runTimeObject() == &Student::objectStudent) { cout << "this a student \n"; } else { cout << "this unknown \n"; } }
上面的静态成员类型为 int,其值并没有实际意义。为了在运行期间记录类的信息,可以用有意义的结构来描述此静态成员,将之命名为 RuntimeObject。
接着,我们就可以把类的最基本信息包括类的名称、大小,添加到 RuntimeObject 中:
QString m_className; // 类的名字 int m_classSize; // 类的大小 RuntimeObject* m_pBaseClass; // 其基类中 RuntimeObject 结构的地址
RuntimeObject 的完整的代码:
runtimeobject.h
#ifndef RUNTIMEOBJECT_H #define RUNTIMEOBJECT_H #include <QString> struct RuntimeObject { RuntimeObject(); bool isDerivedFrom(const RuntimeObject* pBaseClass) const; QString m_className; // 类的名字 int m_classSize; // 类的大小 RuntimeObject* m_pBaseClass; // 其基类中 RuntimeObject 结构的地址 }; #endif // RUNTIMEOBJECT_H
runtimeobject.cpp
#include "runtimeobject.h" RuntimeObject::RuntimeObject() { m_pBaseClass = nullptr; } bool RuntimeObject::isDerivedFrom(const RuntimeObject* pBaseClass) const { const RuntimeObject* pClassThis = this; while(pClassThis != NULL) { if(pClassThis == pBaseClass) // 判断标识类的 RuntimeObject 的首地址是否相同 { return true; } pClassThis = pClassThis->m_pBaseClass; } return false; // 查找到了继承结构的顶层,没有一个匹配 }
要想使所有的类都具有运行期识别的特性,必须有一个类做为继承体系的顶层,也就是说所有具有此特性的类都要从一个类继承,在此将它命名为 MyObject,下面是定义它的代码,也在runtimeobject.h 中。
class MyObject { public: virtual RuntimeObject* runtimeObject() const; virtual ~MyObject(); bool isKindOf(const RuntimeObject* pClass) const; public: static const RuntimeObject classMyObject; // 标识类的静态成员 }; inline MyObject::~MyObject() { } // 下面是一系列的宏定义 // RUNTIME_OBJECT 宏用来取得 class_name 类中 RuntimeObject 结构的地址 #define RUNTIME_OBJECT(class_name) ((RuntimeObject*)&class_name::class##class_name)
runtimeobject.cpp
const struct RuntimeObject MyObject::classMyObject = { "MyObject", sizeof(MyObject), nullptr }; RuntimeObject* MyObject::runtimeObject() const { return RUNTIME_OBJECT(MyObject); } bool MyObject::isKindOf(const RuntimeObject* pClass) const { RuntimeObject* pClassThis = runtimeObject(); return pClassThis->isDerivedFrom(pClass); }
RUNTIME_OBJECT是为了方便访问类的 RuntimeObject 结构而定义的宏。在这里可以看到每个类中 RuntimeObject 成员变量的命名规则:在类名之前冠以 class 作为它的名字。class##classname 中的##告诉编译器,把两个字符串联接在一起。runtimeObject 函数就使用了 RUNTIME_OBJECT 宏。
现在,为了给类添加运行期识别的能力,可以让该类从 MyObject 类继承,然后再在类中添加 RuntimeObject 类型的静态成员等信息。
例如,在下面的例子中, MyStudent 类就具有了运行期识别的能力。
#include <iostream> #include "runtimeobject.h" using namespace std; class MyStudent : public MyObject { public: virtual RuntimeObject* runtimeObject() const { return (RuntimeObject*)&classMyStudent; } static const RuntimeObject classMyStudent; }; const RuntimeObject MyStudent::classMyStudent = { "MyStudent", sizeof(MyStudent), (RuntimeObject*)&MyObject::classMyObject }; int main() { MyObject* pMyObject = new MyStudent; // 判断对象 pMyObject 是否属于 MyStudent 类或者此类的派生类 if(pMyObject->isKindOf(RUNTIME_OBJECT(MyStudent))) { MyStudent* pMyStudent = (MyStudent*)pMyObject; cout << " a student! \n"; delete pMyStudent; } else { delete pMyObject; } }
程序运行后会打印出“a student!”字符串。
MyObject 类的成员函数 isKindOf 可用于确定具体某个对象是否属于指定的类或指定的类的派生类。
要注意, runtimeObject 是虚函数, MyStudent 类重载了它,所以在 isKindOf 函数的实现代码中,“pClassThis = runtimeObject();”语句调用的是MyStudent 类的 runtimeObject函数,而不是 MyObject 类的runtimeObject
因为支持动态类型识别的代码是固定的 ,为了方便用户使用, 最好设计一组宏来代替这些重复性代码。
#define DECLARE_DYNAMIC(class_name) \ public: \ static const RuntimeObject class##class_name; \ virtual RuntimeObject* runtimeObject() const; #define IMPLEMENT_DYNAMIC(class_name, base_class_name) \ const RuntimeObject class_name::class##class_name = { \ #class_name, sizeof(class class_name), \ RUNTIME_OBJECT(base_class_name)}; \ RuntimeObject* class_name::runtimeObject() const \ { return RUNTIME_OBJECT(class_name); }
使用宏替换,代码变得非常简洁,并且不容易出错
class MyStudent : public MyObject { DECLARE_DYNAMIC(MyStudent) }; IMPLEMENT_DYNAMIC(MyStudent, MyObject)
此时,用户不需要知道 MyObject 是什么,不需要知道两个小巧的宏做了些什么,就可以方便地向自己的类中添加动态类型识别的功能。
聪明的同学可能已经发现,上面的写法实际上跟以下Qt QObject的写法是很类似的:
class MyStudent : public QObject { Q_OBJECT //... };
Qt中的元对象系统的作用之一就是支持运行时类型识别,其原理跟以上的做法类似,但它所支持的功能更多更强大,小豆君在这里仅是介绍其基本的实现原理,同时,也以本篇文章为初始为大家分享Qt的元对象系统在实际项目中的应用技巧。
关于本篇文章你应该学习到:
1 类静态成员的用法
2 如何将具体的动态类型识别抽象成通用的动态类型识别代码
3 使用宏简化代码
好了,这次的分享就到这里,我们下次再见,最后不要忘记点赞和分享哦,您的支持就是对原创,分享的最大鼓励。
欢迎关注微信公众号-小豆君Qt分享