自制运行时类型识别系统

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

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

相关文章
|
12月前
|
传感器 机器人
机器人种类知多少
机器人种类知多少
87 0
|
1月前
|
机器学习/深度学习 数据采集 算法
【2024泰迪杯】A 题:生产线的故障自动识别与人员配置 Python代码实现
本文提供了2024泰迪杯A题“生产线的故障自动识别与人员配置”的Python代码实现,包括问题分析、故障数据特征分析、故障报警模型构建、故障时长计算、产量与合格率分析以及操作人员排班方案制定的详细步骤和代码示例。
34 3
【2024泰迪杯】A 题:生产线的故障自动识别与人员配置 Python代码实现
|
11天前
|
自然语言处理 决策智能 Python
同时操控手机和电脑,100项任务,跨系统智能体评测基准有了
【9月更文挑战第9天】近年来,随着人工智能技术的进步,自主智能体的应用日益广泛。为解决现有评测基准的局限性,研究人员推出了CRAB(Cross-environment Agent Benchmark),这是一种支持跨环境任务的新框架,结合了基于图的精细评估方法和高效的任务构建机制。CRAB框架支持多种设备并可轻松扩展至任何具备Python接口的环境。首个跨平台基准CRAB-v0包含100项任务,实验显示GPT-4单智能体在完成率方面表现最佳。CRAB框架为智能体研究提供了新机遇,但也面临计算资源和评估准确性等方面的挑战。
29 9
|
2月前
|
监控 网络协议 安全
由于楼层自动化系统的复杂性和多样性,很难给出一个通用的Python代码示例,因为每个系统可能使用不同的硬件、通信协议和软件接口。
由于楼层自动化系统的复杂性和多样性,很难给出一个通用的Python代码示例,因为每个系统可能使用不同的硬件、通信协议和软件接口。
|
4月前
|
传感器 存储 数据采集
LabVIEW通过视频识别开发布氏硬度机自动化测量系统
LabVIEW通过视频识别开发布氏硬度机自动化测量系统
39 4
|
4月前
|
传感器 数据采集 监控
LabVIEW 开发在不确定路况下自动速度辅助系统
LabVIEW 开发在不确定路况下自动速度辅助系统
18 0
|
4月前
|
存储 数据处理 API
视觉智能平台常见问题之通用视频生成接口声音和画面对不上如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
|
机器学习/深度学习 算法 数据可视化
基于深度学习的智能PCB板缺陷检测系统(Python+清新界面+数据集)
基于深度学习的智能PCB板缺陷检测系统(Python+清新界面+数据集)
656 0
|
机器学习/深度学习 存储 数据可视化
基于卷积神经网络识别金融票据中的文字信息设计GUI界面系统(计算机毕设完整代码)
基于卷积神经网络识别金融票据中的文字信息设计GUI界面系统(计算机毕设完整代码)
134 0
基于卷积神经网络识别金融票据中的文字信息设计GUI界面系统(计算机毕设完整代码)