自制运行时类型识别系统

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

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

目录
打赏
0
0
0
0
5
分享
相关文章
vscode 向下复制当前行功能快捷键 设置成Ctrl + D
vscode 向下复制当前行功能快捷键 设置成Ctrl + D
940 0
详解操作系统四大常用的作业调度算法(FCFS丨SJF丨HRRN丨RR)
详解操作系统四大常用的作业调度算法(FCFS丨SJF丨HRRN丨RR)
5975 0
AI技术深度解析:从基础到应用的全面介绍
人工智能(AI)技术的迅猛发展,正在深刻改变着我们的生活和工作方式。从自然语言处理(NLP)到机器学习,从神经网络到大型语言模型(LLM),AI技术的每一次进步都带来了前所未有的机遇和挑战。本文将从背景、历史、业务场景、Python代码示例、流程图以及如何上手等多个方面,对AI技术中的关键组件进行深度解析,为读者呈现一个全面而深入的AI技术世界。
1017 10
身份验证的三种类型详解
【8月更文挑战第31天】
1230 1
一个 Python 对象会在何时被销毁?
一个 Python 对象会在何时被销毁?
121 2
【Hive】Hive 小文件过多怎么解决?
【4月更文挑战第16天】【Hive】Hive 小文件过多怎么解决?
Vue3+Vite+Pinia+Naive后台管理系统搭建之四:Naive UI 组件库的安装和使用
Vue3+Vite+Pinia+Naive后台管理系统搭建之四:Naive UI 组件库的安装和使用
606 1
号称能打败MLP的KAN到底行不行?数学核心原理全面解析
Kolmogorov-Arnold Networks (KANs) 是一种新型神经网络架构,挑战了多层感知器(mlp)的基础,通过在权重而非节点上使用可学习的激活函数(如b样条),提高了准确性和可解释性。KANs利用Kolmogorov-Arnold表示定理,将复杂函数分解为简单函数的组合,简化了神经网络的近似过程。与mlp相比,KAN在参数量较少的情况下能达到类似或更好的性能,并能直观地可视化,增强了模型的可解释性。尽管仍需更多研究验证其优势,KAN为深度学习领域带来了新的思路。
4476 5
RabbitMQ通过HTTP API获取队列消息内容
RabbitMQ通过HTTP API获取队列消息内容 背景 为配合公司数据中台建设,我们对公司内部使用的mysql binlog数据分发中间件(现已开源:https://gitee.com/kekingcn/keking-binlog-distributor )进行定制化改造,使其能够同时支持re.
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问