Qt 元对象系统 - 反射篇

反射作用

反射的作用就是可以通过字符串创建对象,操作类的成员数据,调用类的方法。
举一个场景例子:
实现一个对象工厂,工厂有个函数叫QObject* createObject(QString className);传递一个className,返回一个该类的对象。
如果没有反射:大概率是这样写

QObject* createObject(QString className){if(className == "A"){return new A();}else if(className == "B"){return new B();}//……
}

这里就不是根据字符串创建对象。因为我们把A(),B()明确地写在代码里了。当有类新增时,我们需要添加新的else if分支。
如果有反射:就可以这样写

QObject* createObject(QString className){MetaObject o = MataObject::fromName(className);return o.newInstance();
}

这里,新增类时,工厂的代码不需要任何改动。
通过字符串新建对象意味着可以运行时才确定创建什么对象。因为字符串可以在运行时才确定,他可能来自用户的输入。没有反射,就只能在工厂里用if else 枚举所有可能的类了。
另一种用途就是可以遍历某个类所有成员变量的名字。这些成员变量的名字可能能用作其他用途,比如在运行时判断一个对象有没有名为xxx的成员变量或者成员函数。比如他们的名字刚好就是对应某个sql语句的数据字段。

QT中的反射

QT的反射是通过MOC编译器进行的,MOC编译器把QT的特殊语法转化为标准编译器认识的C++语法,并添加一些函数实现,从而支持反射。与java的反射不同。java的反射不需要程序员做任何事情就能使用反射,QT的反射则需要程序员给需要被反射的类多写几行代码才能完成。下面的段落会介绍反射类,反射字段,反射方法需要程序员多做哪些事情。

反射字段

反射字段就是通过字符串修改或者访问某个对象的字段,这个字段可以是public,private,protect的。一个字段能被反射,需要具备如下条件。

  1. 所属类直接或者间接继承自QObject。
  2. 类有Q_OBJECT宏定义。
  3. 有Q_PROPERTY指向。
    举例如下:
class Student : public QObject
{Q_OBJECTQ_PROPERTY(QString mName MEMBER mName)
public:explicit Student(QObject *parent = nullptr);QString mName; //可以被反射,因为有Q_PROPERTY指向。(第3行)QString mSex; //不能被反射,因为没有Q_PROPERTY指向。
};

其中mName可以被反射,但mSex不能被反射。

使用反射字段

	Student stu;stu.setProperty("mName","张三");stu.setProperty("mSex","男");qDebug() << stu.mName; //输出张三;qDebug() << stu.mSex;  //输出空字符串。因为mSex字段没有被反射。qDebug() << stu.property("mSex").toString(); //输出男,这里是因为动态地给stu这个对象添加了一个字段mSex,但是它和原本的mSex没有任何关系。stu.mName = "张三变身";qDebug() << stu.property("mName").toString(); //输出张三变身。stu.mSex = "女";qDebug() << stu.property("mSex").toString(); //输出男,之前通过反射添加的字段,并写了男。这里获取的是反射添加的字段的值。

请注意:
Q_PROPERTY(QString mName MEMBER mName)
中,第一个mName是元对象系统中的名字, 第二个才是字段。这个语句把元对象系统中的mName和成员变量进行了关联。 他们名字可以不一样。如果不一样,比如我一开始不小心把第一个mName,打成了nName。结果

stu.setProperty("mName","张三");qDebug() << stu.mName; //输出空。stu.setProperty("nName","张三");  //注意这里是nName。qDebug() << stu.mName; //输出张三。  //这里是mName。

反射自定义类型字段

QT的大部分类型都能反射。如果要反射自定义类型,这个自定义类型必须满足如下条件:

  1. 有拷贝构造函数
  2. 有赋值号运算符重载
  3. 有!=(本类型)运算符重载
  4. Q_DECLARE_METATYPE() 申明
    第一个和第二个可以是默认的。但有时编译器不一定能生成拷贝构造函数或者赋值号构造函数。典型的如继承了QObject的类。因为QObject delete了拷贝构造函数。所以编译器不会为它的派生类生成默认的拷贝构造函数。
    举例如下:
    有一个自定义类
class Teacher
{
public:explicit Teacher(QObject *parent = nullptr);QString mName = "王老师";bool operator!=(const Teacher& other);signals:
};
Q_DECLARE_METATYPE(Teacher)

学生中多了一个字段mTeacher。这个字段的类型是Teacher

class Student : public QObject
{Q_OBJECTQ_PROPERTY(QString mName MEMBER mName)Q_PROPERTY(Teacher mTeacher MEMBER mTeacher)
public:explicit Student(QObject *parent = nullptr);QString mName;QString mSex;Teacher mTeacher;
};

因为Teacher满足了4个条件。(默认的拷贝构造函数,默认的=重载,以及自己写的!=重载 ,Q_DECLARE_METATYPE(Teacher) 申明 )。
所以mTeacher字段可以反射(注意还要有Q_PROPERTY(Teacher mTeacher MEMBER mTeacher))。
其使用起来如下

	Student stu;qDebug() << stu.property("mTeacher").value<Teacher>().mName; //输出王老师  //通过反射得到的字段类型是QVariant,可以通过value模板函数把这个QVariant转化为真实的类型。Teacher otherTeacher;otherTeacher.mName = "李老师";stu.setProperty("mTeacher",QVariant::fromValue<Teacher>(otherTeacher));qDebug() << stu.property("mTeacher").value<Teacher>().mName; //输出李老师 //通过反射得到的字段类型是QVariant,可以通过value模板函数把这个QVariant转化为真实的类型。

还有一点需要提的是,反射字段(stu.property(mName))得到的字段对象是复制来的,也就是说修改反射得来的字段并不会影响原字段。要修改原字段,必须使用setproperty。这将导致深复制。这一点挺糟糕的。

拓展:qRegisterMetaType<Teacher>(“Teacher”);

谈到了Q_DECLARE_METATYPE ,顺便了聊聊qRegisterMetaType 吧。
前者是编译器发挥作用的,后者是运行时发挥作用的。
前者主要解决QVariant 不认识自定义类的问题。有了这个宏, moc就能在QVariant的模板函数中加入这个自定义类的特化。使得编译能过。
后者主要是在运行时把一个键值对加入到一个映射表中。键值对的键是字符串"Teacher"。值是类型Teacher的元对象。这样,qt才能通过字符串创建对象。也就是反射类。后面会说。

反射方法

反射方法就是通过字符串调用一个对象的方法,可以是public,private或者protect。被反射的方法需要具备如下条件。

  1. 类直接或间接继承自QObject
  2. 类有O_OBJECT宏定义
  3. 方法有Q_INVOKABLE 修饰,或者是slots,signals。

举例:
添加了一个showName方法。这是一个无参无返回值的方法。
添加了一个setName方法,这是一个有参数有返回值的方法。

class Student : public QObject
{Q_OBJECTQ_PROPERTY(QString mName MEMBER mName)Q_PROPERTY(Teacher mTeacher MEMBER mTeacher)
public:Q_INVOKABLE explicit Student(QObject *parent = nullptr);QString mName;Q_INVOKABLE bool setName(QString name);Q_INVOKABLE void showName();QString mSex;Teacher mTeacher;
};void Student::showName()
{qDebug()<< "my name is "<<mName;
}bool  Student::setName(QString name)
{qDebug()<< "set name " << name;mName = name;return true;
}

调用

Student stu;
bool result = false;
stu.metaObject()->invokeMethod(&stu,"setName",Qt::DirectConnection,Q_RETURN_ARG(bool,result),Q_ARG(QString,"张三"));
qDebug() << result; //输出true。
stu.metaObject()->invokeMethod(&stu,"showName");

反射类

反射类是通过字符串构建某个类的对象。一个类可以被反射,它必须满足以下条件。

  1. 类直接或间接继承自QObject
  2. 类有O_OBJECT宏定义
  3. 构造函数可以被反射,且是public。

举例:

class Student : public QObject
{Q_OBJECTStudent(const Student& other) = delete;Q_PROPERTY(QString mName MEMBER mName)//Q_PROPERTY(Teacher mTeacher MEMBER mTeacher)
public:Q_INVOKABLE explicit Student(QObject *parent = nullptr);QString mName;Q_INVOKABLE bool setName(QString name);Q_INVOKABLE void showName();QString mSex;//Teacher mTeacher;
};

使用:

auto metaObj = &Student::staticMetaObject;
QObject* stu =metaObj->newInstance();bool result = false;
QMetaObject::invokeMethod(stu,"setName",Qt::DirectConnection,Q_RETURN_ARG(bool,result),Q_ARG(QString,"张三"));
qDebug() << result; //输出true。
QMetaObject::invokeMethod(stu,"showName");

上面还不算是真正的反射,应为我们构建对象不是通过字符串的。实际上我们我们可以建立一个全局Map,键是字符串。值是这个字符串代表的类的元对象。
在程序一开始的时候填充这个全局Map。
refect.h

#ifndef REFLECT_H
#define REFLECT_H
#include<QMetaObject>
#include<QMap>
#include<QString>
class Reflect
{
public:Reflect();template<class T>static void registerMetaObject(QString className){metaObjects.insert(className,&T::staticMetaObject);}static const QMetaObject* fromName(QString className){if(metaObjects.count(className)==0) return nullptr;else return metaObjects[className];}static void init();
private:static QMap<QString,const QMetaObject*> metaObjects;
};#endif // REFLECT_H

refect.cpp

#include "reflect.h"
#include <student.h>
QMap<QString,const QMetaObject*> Reflect::metaObjects;#define reg(className) registerMetaObject<className>(#className);
Reflect::Reflect() {}void Reflect::init()
{reg(Student);
}

使用

Reflect::init();
auto metaObj = Reflect::fromName("Student");
QObject* stu =metaObj->newInstance();// Student stu;
bool result = false;
QMetaObject::invokeMethod(stu,"setName",Qt::DirectConnection,Q_RETURN_ARG(bool,result),Q_ARG(QString,"张三"));
qDebug() << result; //输出true。
QMetaObject::invokeMethod(stu,"showName");

qRegisterMetaType

它的用法是这样的:

qRegisterMetaType<Student>("Student");

作用类似于自己写的
Reflect::registerMetaObject(QString className);
为什么要自己写呢?原因是这个函数它要求Student有拷贝构造函数。但是Student默认是拷贝构造函数的,因为它继承自QObject。而QObject默认没有拷贝构造函数。在有些时候一些类我们不希望提供拷贝构造函数。

我的QT 版本是5.14。更高的版本的qRegisterMetaType可能没有这个问题了。qRegisterMetaType的实现中发生了对类的拷贝,所以要求有拷贝构造函数,这个违反直觉,谁能想到为什么qRegisterMetaType要拷贝对象?

反射的坏处

不会进行编译时检查。反射通过字符串创建一个类,如果字符串拼错了。编译是没有任何报错的。
运行时可能导致程序崩溃,因为不存在这么一个类。返回了一个空指针。后面也没有判空处理。运行后就只有一个崩溃。往往需要很长时间才 能排查到拼错了一个单词。这种在反射字段时更加隐蔽,因为反射字段时拼错一个单词不会造成崩溃,而且程序上没有错误。只是莫名奇妙的有个值没有赋上。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

7-简单巡检

KES的版本与license有效期 简单而又会产生灾难性的问题 使用version函数查看KES版本信息 test# select version();查看license有效期 test# select get_license_validdays(); 服务器的时区和时间 查看KES服务器的时区 test# show timezone; test# show time_zone; #两者皆…

【金融风控项目-07】:业务规则挖掘案例

文章目录 1.规则挖掘简介2 规则挖掘案例2.1 案例背景2.2 规则挖掘流程2.3 特征衍生2.4 训练决策树模型2.5 利用结果划分分组 1.规则挖掘简介 两种常见的风险规避手段&#xff1a; AI模型规则 如何使用规则进行风控 **使用一系列逻辑判断(以往从职人员的经验)**对客户群体进行区…

RabbitMQ高可用

生产者确认 生产者确认就是&#xff1a;发送消息的人&#xff0c;要确保消息发送给了消息队列&#xff0c;分别是确保到了交换机&#xff0c;确保到了消息队列这两步。 1、在发送消息服务的application.yml中添加配置 spring:rabbitmq:publisher-confirm-type: correlated …

Redis的特性ubuntu进行安装

文章目录 1.六大特性1.1内存存储数据1.2可编程1.3可扩展1.4持久化1.5集群1.6高可用1.7速度快 2.具体应用场景&#xff08;了解&#xff09;3.Ubuntu安装Redis3.1安装指令3.2查看状态3.3查找配置文件3.4修改文件内容3.5重启服务器生效3.6安装客户端并进行检查 4.Redis客户端介绍…

本地音乐服务器(三)

6. 删除音乐模块设计 6.1 删除单个音乐 1. 请求响应设计 2. 开始实现 首先在musicmapper新增操作 Music findMusicById(int id);int deleteMusicById(int musicId); 其次新增相对应的.xml代码&#xff1a; <select id"findMusicById" resultType"com.exa…

leetcode【滑动窗口】相关题目分析讲解:leetcode209,leetcode904

经典滑动窗口(leetcode209) 题干 题目难度&#xff1a;简单 题目分析 要求找到符合大于等于target的长度最小的子数组的常规思路便是暴力破解——遍历数组&#xff0c;通过两层遍历&#xff0c;找到最小的子数组并返回。 但显而易见&#xff0c;这样时间复杂度会是O(n2)级…

ArkTS组件结构和状态管理

1. 认识基本的组件结构 ArkTS通过装饰器Component 和Entry 装饰 struct 关键字声明的数据结构&#xff0c;构成一个自定义组件 自定义组件中提供了一个build函数&#xff0c;开发者需要在函数内以链式调用的方式进行基本的UI描述&#xff0c;UI描述的方法请参考UI描述规范srtuc…

语义分割(semantic segmentation)

语义分割(semantic segmentation) 文章目录 语义分割(semantic segmentation)图像分割和实例分割代码实现 语义分割指将图片中的每个像素分类到对应的类别&#xff0c;语义区域的标注和预测是 像素级的&#xff0c;语义分割标注的像素级的边界框显然更加精细。应用&#xff1a…

【STM32】在 STM32 USB 设备库添加新的设备类

说实话&#xff0c;我非常想吐槽 STM32 的 USB device library&#xff0c;总感觉很混乱。 USB Device library architecture 根据架构图&#xff1a; Adding a custom class 如果你想添加新的设备类&#xff0c;必须修改的文件有 usbd_desc.cusbd_conf.cusb_device.c 需要…

【母线槽分类与选型】

母线槽是一种高效、安全、节能的输电设备&#xff0c;广泛应用于各类建筑和工业领域。母线槽可以根据不同的分类方式进行划分&#xff0c;例如根据其结构、用途、导体材质等。母线槽以铜或铝作为导体、用非烯性绝缘支撑&#xff0c;然后装到金属槽中而形成的新型导体。在高层建…

一些任务调度的概念杂谈

任务调度 1.什么是调度任务 依赖&#xff1a;依赖管理是整个DAG调度的核心。调度依赖包括依赖策略和依赖区间。 依赖分为任务依赖和作业依赖&#xff0c;任务依赖是DAG任务本身的依赖关系&#xff0c;作业依赖是根据任务依赖每天的作业产生的。两者在数据存储模型上有所不同…

Unifying Top-down and Bottom-up Scanpath Prediction Using Transformers

Abstract 大多数视觉注意力模型旨在预测自上而下或自下而上的控制&#xff0c;这些控制通过不同的视觉搜索和自由观看任务进行研究。本文提出了人类注意力变换器&#xff08;Human Attention Transformer&#xff0c;HAT&#xff09;&#xff0c;这是一个能够预测两种形式注意力…

解决MindSpore-2.4-GPU版本的安装问题

问题背景 虽说在MindSpore-2.3之后的版本中不在正式的发行版中支持GPU硬件后端&#xff0c;但其实在开发分支版本中对GPU后端是有支持的&#xff1a; 但是在安装的过程中可能会遇到一些问题或者报错&#xff0c;这里复现一下我的Ubuntu-20.04环境下的安装过程。 Pip安装 基本的…

【拥抱AI】如何使用BERT等预训练模型计算语义相似度

使用BERT等预训练模型计算语义相似度是一种非常有效的方法&#xff0c;可以捕捉句子之间的深层次语义关系。下面是一个详细的步骤指南&#xff0c;介绍如何使用BERT和Sentence-BERT来计算语义相似度。 1. 环境准备 1.1 安装必要的库 首先&#xff0c;确保你已经安装了必要的…

Excel常用技巧分享

excel单元格内换行 直接按回车会退出当前单元格的编辑&#xff0c;如果需要在单元格中换行&#xff0c;需要按下AltEnter。 excel插入多行或多列 WPS 在WPS中想要插入多行&#xff0c;只需在右键菜单中输入对应的数字即可。 Office Excel excel中相对麻烦一些&#xff0c;比…

C# .NET环境下调用ONNX格式YOLOV8模型问题总结

我的环境是&#xff1a; Visual Studio: 2019 显卡&#xff1a; 一、遇到问题 1、EntryPointNotFoundException:无法在DLL“onnxruntime”中找到名为“OrtGetApiBase”的入口点。差了下原因&#xff0c;入口点是启动项中的问题。 原因&#xff1a;之前用yolov7时安装的版本在C…

【PTA】【数据库】【SQL命令】编程题1

数据库SQL命令测试题1 10-1 显示教工编号以02开头的教师信息 作者 冰冰 单位 广东东软学院 显示教工编号以02开头的教师信息 提示&#xff1a;请使用SELECT语句作答。 表结构: CREATE TABLE teacher ( TId CHAR(5) NOT NULL, -- 教师工号&#xff0c;主键 DId CHAR(2) …

VSCode快速生成vue组件模版

1&#xff0c;点击设置&#xff0c;找到代码片段 2&#xff0c;搜索vue&#xff0c;打开vue.json 3&#xff0c;添加模版 vue2模板 "vue2": {"prefix": "vue2","body": ["<template>"," <div>$0</di…

理解DOM:前端开发的基础

理解DOM 在前端开发中&#xff0c;DOM&#xff08;文档对象模型&#xff09;是一个至关重要的概念。它不仅定义了如何通过编程方式访问和修改网页内容&#xff0c;还为我们提供了一种结构化的方式来与页面交互。本文将带你了解DOM的基本概念、不同节点的操作以及何时可以进行更…

如何将几个音频合成一个音频?非常简单的几种合成方法

如何将几个音频合成一个音频&#xff1f;音频合成不仅仅是将不同的音频文件按顺序排列&#xff0c;它还可能涉及到音量调节、剪辑、淡入淡出、音效调整等多个方面。对于一些专业的音频制作人员来说&#xff0c;音频的每一部分细节都可能需要精心打磨&#xff0c;以确保最终合成…