自制运行时类型识别系统

简介: 自制运行时类型识别系统

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分享

相关文章
|
机器学习/深度学习 人工智能 自然语言处理
AI技术深度解析:从基础到应用的全面介绍
人工智能(AI)技术的迅猛发展,正在深刻改变着我们的生活和工作方式。从自然语言处理(NLP)到机器学习,从神经网络到大型语言模型(LLM),AI技术的每一次进步都带来了前所未有的机遇和挑战。本文将从背景、历史、业务场景、Python代码示例、流程图以及如何上手等多个方面,对AI技术中的关键组件进行深度解析,为读者呈现一个全面而深入的AI技术世界。
1969 10
|
10月前
|
数据采集 人工智能 Ubuntu
MyEMS开源能源管理系统开发工具
MyEMS 是一款基于 ISO 50001 标准的开源能源管理系统,支持建筑、工厂等场景的电、水、气等能源数据采集与分析,提供光伏、储能、微电网、AI 优化等功能。系统采用开源工具开发,包括 PyCharm、VS Code、Docker、Ubuntu 等,并搭配 MySQL 数据库及 DBeaver 客户端,助力企业实现低碳发展。社区版完全免费,适合开发者学习和部署。下载地址:[https://gitee.com/myems/myems](./https://gitee.com/myems/myems)。
495 32
|
缓存 Java 程序员
一个 Python 对象会在何时被销毁?
一个 Python 对象会在何时被销毁?
261 2
|
人工智能 弹性计算 IDE
你用AI编程用到什么深度了?
你用AI编程用到什么深度了?
你用AI编程用到什么深度了?
|
存储 安全 生物认证
身份验证的三种类型详解
【8月更文挑战第31天】
2432 1
|
SQL 缓存 API
在API接口数据获取过程中,如何确保数据的安全性和隐私性?
在API接口数据获取过程中,确保数据的安全性和隐私性至关重要。本文介绍了身份认证与授权、防止SQL注入和XSS攻击、加密传输、API版本控制、限流与熔断、压力测试与性能优化、备份与恢复以及法律和伦理考量等关键措施,帮助开发者和管理者有效保护API接口的数据安全和隐私性。
|
SQL 存储 算法
【Hive】Hive 小文件过多怎么解决?
【4月更文挑战第16天】【Hive】Hive 小文件过多怎么解决?
Vue3+Vite+Pinia+Naive后台管理系统搭建之四:Naive UI 组件库的安装和使用
Vue3+Vite+Pinia+Naive后台管理系统搭建之四:Naive UI 组件库的安装和使用
1328 1
|
容器
【Qt 学习笔记】Qt常用控件 | 容器类控件 | Tab Widget的使用及说明
【Qt 学习笔记】Qt常用控件 | 容器类控件 | Tab Widget的使用及说明
2518 2

热门文章

最新文章