QT 中的元对象系统(一):元对象和元数据

目录

1.为什么需要元系统

2.元数据

3.模拟元对象系统

3.1.元对象声明

3.2.对C++扩展

3.3初始化元对象

3.4.使用元对象

4.QT的元系统

4.1.元对象系统基于QObject类、Q_OBJECT宏、元对象编译器MOC实现

4.2.元对象系统的功能

4.3.Q_PROPERTY()的使用

4.4.Q_INVOKABLE使用


1.为什么需要元系统

        Qt 作为跨平台的GUI框架,在实际项目中应用广泛,在日常的使用中,随手使用的一些机制(如著名的信号槽机制),属性(如Property系统),以及重载各种事件函数来完成定制化;还有qml中直接访问QObject的Property。

        在Qt项目中,可以直接通过类名创建对象:

class MyClass : public QObject {Q_OBJECTQ_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)
public:MyClass(QObject *parent = nullptr) : QObject(parent) {}void myMethod() { qDebug() << "Hello, world!"; }
};//根据类名创建对象
const QMetaObject *metaObject = MyClass::staticMetaObject();
QObject *myObject = metaObject->newInstance(Q_ARG(QObject*, nullptr));

        可以函数名直接访问类的方法:

QMetaObject::invokeMethod(myObject, "myMethod");

        运行时增加属性,在运行时根据当前的上下文为一个对象增加或者删除属性,并且要做到在其他地方使用的时候无感——就像这个属性原来就声明在类中一样:

MyClass obj;
obj.setProperty("age", 10); //固定属性,事先声明定义好的
obj.setProperty("name", 110); //动态属性

等等,有了元系统,使得在掌握很少类内部信息都能完成类数据的获取和方法的调用。

2.元数据

元数据是描述数据的数据,它提供了关于数据对象的附加信息。在Qt中,元数据通常用于描述类的属性、方法、信号和槽等信息。试想一下,我们会怎么描述一个类 MyClass:

class MyClass : public Object
{
public:MyClass();~MyClass();enum Type{//... };
public:virtual void fool() override;void bar();//...
};
  • 这个类的类名为MyClass
  • 继承了一个基类 Object
  • 有一个无参的构造函数和一个析构函数
  • 实现了继承来的一个虚方法
  • 自己有一个名为bar的public方法
  • 内定义了一个枚举类型
  • ...

上述描述内容就是元数据,用来描述我们声明的一个class,如果我们把以上数据封装为一个类,我们简单的认为这个类就是元对象。

3.模拟元对象系统

Qt 的元对象系统发展这么久,完善是真的完善,代码多也是真的多!在迷失于复杂繁琐的源代码中之前,不妨先来设计一个简单的元对象系统来帮助我们理解思想。

3.1.元对象声明

联系前面的元数据的说明,朴素的想法是我们可以用另一个对象来描述这些信息,即元对象,在运行时通过这个对象来获取相关的具体类型等。

根据我们的需要,元对象应该具有以下信息

  • 类型名
  • 继承的父类信息
  • 成员函数的信息
  • 内部定义的枚举变量可能也是需要的
  • ...

看起来像是这样

class MetaObject
{
public:// 其他成员函数// ...private:// 简单起见,直接用对象了ClassInfo m_info;ClassInfo* m_superClass;ClassMethod m_methods;ClassEnums m_enums;
};

3.2.对C++扩展

为了使我们能在软件系统中有效的管理,我们需要对MyClass做一些拓展,现在MyClass看上去像这样:

// MyClass.h
class MyClass : public Object
{// ... 和之前一样// 重写一个来自Object的虚方法virtual const MetaObject *metaObject() const override;static const MetaObject staticMetaObject;   // 一个静态成员
};

现在,只要这个数据能够正确初始化,如果我们需要,我们就可以借助多态的特性,通过接口来获得这个类的相关信息了。

3.3初始化元对象

那么问题来了,怎么初始化这个变量呢,C++ 作为静态语言,想要获取这些编译期有关的信息,我们只能选择在编译时或者编译前来做这件事,直觉告诉我们,我们要做编译器之前来做这件事,有两个显而易见的原因

  1. 不要妄图修改编译器,成本巨大且危险
  2. 直接修改编译器显示不是用户能接受的方式

当然可以手动编写这个文件,把类的信息一个个提炼出来,但是那样太不程序员了,我们需要写一段程序,在编译器之前来做这个事情(你可以把它当成一段生成代码的脚本),我们可以这样做:

  1. 在我们写的类里面加上一个标记,来表示该类使用了元对象,需要处理并正确初始化 MetaObejct,我们这里假设就用 DEBUG_OBJ 来表示
  2. 运行我们的程序,如果在某个文件里面发现了标记,解析这个文件,获取他的类型信息(ClassInfo),方法信息(ClassMethod),继承信息等
  3. 脚本生成了一个 moc_MyClass.cpp 文件,用上述信息初始化 MetaObject,类似于下面这样:
// 由脚本生成的文件
// moc_MyClass.cpp
#include "MyClass.h"// 这里是脚本解析原来头文件生成的数据
// 解析了类的名称,成员,继承关系等等
// ...const MetaObject MyClass::staticMetaObject = {// 用解析来的数据来初始化元对象内容
};const MetaObject *MyClass::metaObject() const
{return &staticMetaObject;
}

然后把这个文件也为做源文件一起编译就行了。

3.4.使用元对象

现在再回头来看前面的问题

1)现在直接通过虚函数多态性质拿到 MetaObject,再拿到元数据,比较两个类名是不是一致即可,如果我们采用静态的字符串数组来存类名,甚至我们不需要比较字符串是否一致,只需要比较字符串指针是否相同就可以了。

2)现在直接绑定两个对象的方法字符串即可,我们可以在 MetaObject 提供两各方法

  • 检查这两个字符串是否是类的方法(ClassMethod中有没有这个字符串以及参数检查),以判断绑定是否能成功
  • 一个统一的调用形式,内部根据字符串来调用相关方法

3)现在你可添加属性,实际添加到元数据中,而存取就像你调用get,set方法一样自然

大功告成,至此,一个简单的元对象系统就设计好了!

4.QT的元系统

    4.1.元对象系统基于QObject类、Q_OBJECT宏、元对象编译器MOC实现

    1) QObject 类
    作为每一个需要利用元对象系统的类的基类。
    2) Q_OBJECT宏
    定义在每一个类的私有数据段,用来启用元对象功能,比如动态属性、信号和槽。
    在一个QObject类或者其派生类中,如果没有声明Q_OBJECT宏,那么类的metaobject对象不会被生成,类实例调用metaObject()返回的就是其父类的metaobject对象,导致的后果是从类的实例获得的元数据其实都是父类的数据。因此类所定义和声明的信号和槽都不能使用,所以,任何从QObject继承出来的类,无论是否定义声明了信号、槽和属性,都应该声明Q_OBJECT 宏。
    3) 元对象编译器MOC (Meta Object Complier)
    MOC分析C++源文件,如果发现在一个头文件(header file)中包含Q_OBJECT 宏定义,会动态的生成一个moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的实现代码,会被编译、链接到类的二进制代码中,作为类的完整的一部分。

    4.2.元对象系统的功能

    qt元对象系统主要提供了三个能力:

    • 对象间通信(信号槽机制)
    • 运行时信息(类似反射机制)
    • 动态的属性系统

    除了这些功能外,还提供了如下功能:

    • QObject::metaObject() 返回与该类相关联的元对象。
    • QMetaObject::className() 在运行时以字符串形式返回类名,而无需通过 C++ 编译器提供本地运行时类型信息(RTTI)支持。
    • QObject::inherits() 函数返回一个对象是否是在 QObject 继承树内继承了指定类的实例。
    • QObject::tr() 和 QObject::trUtf8() 用于国际化的字符串翻译。
    • QObject::setProperty() 和 QObject::property() 动态地通过名称设置和获取属性。
    • QMetaObject::newInstance() 构造该类的新实例。
    •  使用qobject_cast()方法在QObject类之间提供动态转换,qobject_cast()方法的功能类似于标准C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持

    4.3.Q_PROPERTY()的使用

    #define Q_PROPERTY(text)

    Q_PROPERTY定义在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC处理。

    Q_PROPERTY(type nameREAD getFunction[WRITE setFunction][RESET resetFunction][NOTIFY notifySignal][REVISION int][DESIGNABLE bool][SCRIPTABLE bool][STORED bool][USER bool][CONSTANT][FINAL])

    Type:属性的类型
    Name:属性的名称
    READ getFunction:属性的访问函数
    WRITE setFunction:属性的设置函数
    RESET resetFunction:属性的复位函数
    NOTIFY notifySignal:属性发生变化的地方发射的notifySignal信号
    REVISION int:属性的版本,属性暴露到QML中
    DESIGNABLE bool:属性在GUI设计器中是否可见,默认为true
    SCRIPTABLE bool:属性是否可以被脚本引擎访问,默认为true
    STORED bool:
    USER bool:
    CONSTANT:标识属性的值是常量,值为常量的属性没有WRITE、NOTIFY
    FINAL:标识属性不会被派生类覆写
    注意:NOTIFY notifySignal声明了属性发生变化时发射notifySignal信号,但并没有实现,因此程序员需要在属性发生变化的地方发射notifySignal信号。
    Object.h:

    #ifndef OBJECT_H
    #define OBJECT_H#include <QObject>
    #include <QString>
    #include <QDebug>class Object : public QObject
    {Q_OBJECTQ_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)Q_PROPERTY(int score READ score  WRITE setScore NOTIFY scoreChanged)Q_CLASSINFO("Author", "Scorpio")Q_CLASSINFO("Version", "1.0")Q_ENUMS(Level)
    protected:QString m_name;QString m_level;int m_age;int m_score;
    public:enum Level{Basic,Middle,Advanced};
    public:explicit Object(QString name, QObject *parent = 0):QObject(parent){m_name = name;setObjectName(m_name);connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));}int age()const{return m_age;}void setAge(const int& age){m_age = age;emit ageChanged(m_age);}int score()const{return m_score;}void setScore(const int& score){m_score = score;emit scoreChanged(m_score);}
    signals:void ageChanged(int age);void scoreChanged(int score);
    public slots:void onAgeChanged(int age){qDebug() << "age changed:" << age;}void onScoreChanged(int score){qDebug() << "score changed:" << score;}
    };#endif // OBJECT_H

    Main.cpp:

    #include <QCoreApplication>
    #include "Object.h"int main(int argc, char *argv[])
    {QCoreApplication a(argc, argv);Object ob("object");//设置属性ageob.setProperty("age", QVariant(30));qDebug() << "age: " << ob.age();qDebug() << "property age: " << ob.property("age").toInt();//设置属性scoreob.setProperty("score", QVariant(90));qDebug() << "score: " << ob.score();qDebug() << "property score: " << ob.property("score").toInt();//内省intropection,运行时查询对象信息qDebug() << "object name: " << ob.objectName();qDebug() << "class name: " << ob.metaObject()->className();qDebug() << "isWidgetType: " << ob.isWidgetType();qDebug() << "inherit: " << ob.inherits("QObject");return a.exec();
    }

    4.4.Q_INVOKABLE使用

    #define Q_INVOKABLE

            Q_INVOKABLE定义在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC识别。
            Q_INVOKABLE宏用于定义一个成员函数可以被元对象系统调用,Q_INVOKABLE宏必须写在函数的返回类型之前。如下:

    Q_INVOKABLE void invokableMethod();

            invokableMethod()函数使用了Q_INVOKABLE宏声明,invokableMethod()函数会被注册到元对象系统中,可以使用 QMetaObject::invokeMethod()调用。
            Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起,在Qt C++/QML混合编程、跨线程编程、Qt Service Framework以及 Qt/ HTML5混合编程以及里广泛使用。
    1) 在跨线程编程中的使用
    如何调用驻足在其他线程里的QObject方法呢?Qt提供了一种非常友好而且干净的解决方案:向事件队列post一个事件,事件的处理将以调用所感兴趣的方法为主(需要线程有一个正在运行的事件循环)。而触发机制的实现是由MOC提供的内省方法实现的。因此,只有信号、槽以及被标记成Q_INVOKABLE的方法才能够被其它线程所触发调用。如果不想通过跨线程的信号、槽这一方法来实现调用驻足在其他线程里的QObject方法。另一选择就是将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。
    2) Qt Service Framework
    Qt服务框架是Qt Mobility 1.0.2版本推出的,一个服务(service)是一个独立的组件提供给客户端(client)定义好的操作。客户端可以通过服务的名称,版本号和服务的对象提供的接口来查×××。 查找到服务后,框架启动服务并返回一个指针。
    服务通过插件(plug-ins)来实现。为了避免客户端依赖某个具体的库,服务必须继承自QObject,保证QMetaObject 系统可以用来提供动态发现和唤醒服务的能力。要使QmetaObject机制充分的工作,服务必须满足,其所有的方法都是通过 signal、slot、property或invokable method和Q_INVOKEBLE来实现。

    QServiceManager manager;
    QObject *storage ;  
    storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); 
    if(storage)     QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt")); 

    上述代码通过service的元对象提供的invokeMethod方法,调用文件存储对象的deleteFile() 方法。客户端不需要知道对象的类型,因此也没有链接到具体的service库。 当然在服务端的deleteFile方法,一定要被标记为Q_INVOKEBLE,才能够被元对象系统识别。
    Qt服务框架的一个亮点是它支持跨进程通信,服务可以接受远程进程。在服务管理器上注册后,进程通过signal、slot、invokable method和property来通信,就像本地对象一样。服务可以设定为在客户端间共享,或针对一个客户端。 在Qt服务框架推出之前,信号、槽以及invokable method仅支持跨线程。 下图是跨进程的服务/客户段通信示意图。invokable method和Q_INVOKEBLE 是跨进城、跨线程对象之间通信的重要利器。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/70658.shtml

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    Pytorch实现之浑浊水下图像增强

    简介 简介:这也是一篇非常适合GAN小白们上手的架构文章!提出了一种基于GAN的水下图像增强网络。这种网络与其他架构类似,生成器是卷积+激活函数+归一化+残差结构的组成,鉴别器是卷积+激活函数+归一化以及全连接层。损失函数是常用的均方误差、感知损失和对抗损失三部分。 …

    TCPDF 任意文件读取漏洞:隐藏在 PDF 生成背后的危险

    在网络安全的世界里&#xff0c;漏洞就像隐藏在黑暗中的“定时炸弹”&#xff0c;稍有不慎就会引发灾难性的后果。今天&#xff0c;我们要聊的是一个与 PDF 生成相关的漏洞——TCPDF 任意文件读取漏洞。这个漏洞可能让攻击者轻松读取服务器上的敏感文件&#xff0c;甚至获取整个…

    【Git】六、企业级开发模型

    文章目录 Ⅰ. 前言Ⅱ. 系统开发环境Ⅲ. Git 分支设计规范master分支release分支develop分支feature分支hotfix分支 Ⅰ. 前言 ​ 我们知道&#xff0c;一个软件从零开始到最终交付&#xff0c;大概包括以下几个阶段&#xff1a;规划、编码、构建、测试、发布、部署和维护。 ​…

    Kafka可视化工具EFAK(Kafka-eagle)安装部署

    Kafka Eagle是什么&#xff1f; Kafka Eagle是一款用于监控和管理Apache Kafka的开源系统&#xff0c;它提供了完善的管理页面&#xff0c;例如Broker详情、性能指标趋势、Topic集合、消费者信息等。 源代码地址&#xff1a;https://github.com/smartloli/kafka-eagle 前置条件…

    C++:dfs,bfs各两则

    1.木棒 167. 木棒 - AcWing题库 乔治拿来一组等长的木棒&#xff0c;将它们随机地砍断&#xff0c;使得每一节木棍的长度都不超过 5050 个长度单位。 然后他又想把这些木棍恢复到为裁截前的状态&#xff0c;但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序…

    电子商务网站租用香港服务器的好处有哪些?

    电子商务网站租用香港服务器的好处主要包括&#xff1a; 香港服务器提供高速的网络连接&#xff0c;国内访问速度优势明显&#xff0c;满足企业内部数据传输和远程办公需求。拥有国际出口带宽优势&#xff0c;实现与全球各地的高速连接&#xff0c;对跨国业务和海外市场拓展至关…

    stm32108键C-B全调性_动态可视化乐谱钢琴

    108键全调性钢琴 一 基本介绍1 项目简介2 实现方式3 项目构成 二 实现过程0 前置基本外设驱动1 声音控制2 乐谱录入&基础乐理3 点阵屏谱点动态刷新4 项目交互控制5 录入新曲子过程 三 展示&#xff0c;与链接视频地址1 主要功能函数一览2 下载链接3 视频效果 一 基本介绍 …

    【p-camera-h5】 一款开箱即用的H5相机插件,支持拍照、录像、动态水印与样式高度定制化。

    【开源推荐】p-camera-h5&#xff1a;一款轻量级H5相机插件开发实践 一、插件背景 在Web开发中&#xff0c;原生摄像头功能的集成往往面临以下痛点&#xff1a; 浏览器兼容性问题视频流与水印叠加实现复杂移动端适配困难功能定制成本高 为此&#xff0c;p-camera-h5 —— 一…

    交叉编译curl(OpenSSL)移植ARM详细步骤

    运行配置脚本 使用 Configure 脚本配置 OpenSSL&#xff0c;指定目标平台和安装路径&#xff1a; curl downloads 各个版本 Old 1.1.1 Releases | OpenSSL Library 各个版本 从 OpenSSL 官网下载源码包 tar -xzf openssl-1.1.1b.tar.gz cd openssl-1.1.1b/运行配置脚本 使…

    大语言模型中的 Token如何理解?

    在大语言模型中&#xff0c;Token 是文本处理的基本单元&#xff0c;类似于“文字块”&#xff0c;模型通过将文本分割成Token来理解和生成内容。举一个形象一点的例子&#xff0c;可以理解为 AI 处理文字时的“最小积木块”。就像搭乐高时&#xff0c;每块积木是基础单位一样&…

    表单制作代码,登录动画背景前端模板

    炫酷动效登录页 引言 在网页设计中,按钮是用户交互的重要元素之一。一个炫酷的按钮特效不仅能提升用户体验,还能为网页增添独特的视觉吸引力。今天,我们将通过CSS来实现一个“表单制作代码,登录动画背景前端模板”。该素材呈现了数据符号排版显示出人形的动画效果,新颖有…

    1分钟用DeepSeek编写一个PDF转Word软件

    一、引言 如今&#xff0c;在线工具的普及让PDF转Word成为了一个常见需求&#xff0c;常见的pdf转word工具有收费的wps&#xff0c;免费的有pdfgear&#xff0c;见下文&#xff1a; PDFgear:一款免费的PDF编辑、格式转化软件-CSDN博客 还有网上在线的免费pdf转word工具smallp…

    【找工作】C++和算法复习(自用)

    文章目录 C头文件自定义排序函数stl 算法数据结构树状数组 数学字符串manacherkmp 自用随便记录 C 排序 stl 头文件 全能头文件&#xff1a; #include<bits/stdc.h>自定义排序函数 bool compare(const int &odd1,const int &odd2) {return odd1>odd2; }…

    请谈谈 React 中的状态管理,如何使用 Context API 和 Redux 进行状态管理?

    一、Context API 深度应用 1. 核心实现原理 通过createContext创建上下文对象&#xff0c;使用Provider组件包裹需要共享状态的组件树&#xff0c;子组件通过useContext Hook或Consumer组件消费数据。 代码示例&#xff08;主题切换场景&#xff09;&#xff1a; // 创建上…

    kotlin的Int类型调用toString()会导致空指针吗

    DeepSeek R1 思考 38 秒 思考过程 用户的问题是关于Kotlin中Int类型调用toString()是否会导致空指针异常。首先&#xff0c;我需要回忆Kotlin的基本类型系统。Kotlin中的Int属于基本数据类型&#xff0c;但它在语言层面被表示为非空类型。也就是说&#xff0c;Int变量本身不能…

    网络运维学习笔记 017 HCIA-Datacom综合实验01

    文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置&#xff08;IP二层VLAN链路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…

    基于Hadoop的汽车大数据分析系统设计与实现【爬虫、数据预处理、MapReduce、echarts、Flask】

    文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍爬虫数据概览HIve表设计Cars Database Tables 1. cars_data2. annual_sales_volume3. brand_sales_volume4. city_sales_volume5. sales_volume_by_year_and_brand6. sales_distri…

    springboot实现多文件上传

    springboot实现多文件上传 代码 package com.sh.system.controller;import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMap…

    Java所有运算符理解

    Java 运算符 算术运算符 表格中的实例假设整数变量A的值为10&#xff0c;变量B的值为20&#xff1a; 操作符描述例子加法 - 相加运算符两侧的值A B 等于 30-减法 - 左操作数减去右操作数A – B 等于 -10*乘法 - 相乘操作符两侧的值A * B等于200/除法 - 左操作数除以右操作数…

    纷析云:赋能企业财务数字化转型的开源解决方案

    在企业数字化转型的浪潮中&#xff0c;财务管理的高效与安全成为关键。纷析云凭借其开源、安全、灵活的财务软件解决方案&#xff0c;为企业提供了一条理想的转型路径。 一、开源的力量&#xff1a;自主、安全、高效 纷析云的核心优势在于其100%开源的财务软件源码。这意味着…