使用c++函数式编程实现Qt信号槽机制
问题背景
在下面的代码中,Input输入器 输入数据,希望A和B 接收数据。但使用的赋值,导致in.a和a只是拷贝数据,而不是同一个对象,使得数据不同步。
#include <iostream>
struct A
{int age = 32;
};
struct B
{int age = 10;
};struct Input
{void fun(int i){a.age = i;b.age = i;}A a;B b;
};
int main(int argc, char *argv[])
{Input in;A a;B b;in.a = a;in.b = b;in.fun(3);std::cout<<a.age<<" "<<b.age<<std::endl;//32 10return 0;
}
解决方法1:如下所示,当希望修改in.a的age时能修改到A a的age,需要传指针A,而且还要手动指定in.a = &a
#include <iostream>
struct A
{int age = 32;
};
struct Input
{void fun(int i){a->age = i;std::cout<<a->age<<std::endl;}A* a;
};
int main(int argc, char *argv[])
{Input in;A a;in.a = &a;in.fun(4);//4return 0;
}
解决方法2:使用function实现回调函数,将fun函数的赋值操作写在回调函数中
#include <iostream>
#include<functional>
struct A
{int age = 32;
};
struct Input
{void fun(int i){callback(i);}std::function<void(int)> callback;
};
int main(int argc, char *argv[])
{Input in;A a;in.callback = [&a](int i){a.age = i;std::cout<<a.age<<std::endl;};in.fun(4);//4return 0;
}
方法3:将添加回调函数和执行回调函数抽离出来,实现成Signal信号的形式
#include <iostream>
#include<functional>
#include <vector>
struct Signal
{std::vector<std::function<void(int)>> m_callbacks;void connect(std::function<void(int i)> callback){m_callbacks.push_back(std::move(callback));}void emit(int i){for(auto&& callback: m_callbacks){callback(i);}}
};
struct A
{int age = 32;
};
struct Input
{void fun(int i){on_input.emit(i);}Signal on_input;
};
int main(int argc, char *argv[])
{Input in;A a;in.on_input.connect([&a](int i){a.age = i;std::cout<<a.age<<std::endl;});in.on_input.connect([&a](int i){a.age = i;std::cout<<a.age<<std::endl;});in.fun(4);//4return 0;
}
4:如果类A需要注册一个退出事件on_exit,有如下实现。但实际上我们并不希望在此信号传递参数int i。
#include <iostream>
#include <functional>
#include <vector>
struct Signal
{std::vector<std::function<void(int)>> m_callbacks;void connect(std::function<void(int i)> callback){m_callbacks.push_back(std::move(callback));}void emit(int i){for(auto&& callback: m_callbacks){callback(i);}}
};
struct A
{ void on_input(int i) const {std::cout<<"input "<<age<<std::endl;}void on_exit() const {std::cout<<"exit "<<std::endl;}int age = 32;
};
struct Input
{//调用该函数就发出 进入事件和退出事件的信号void fun(int i){on_input.emit(i);on_exit.emit(i);}Signal on_input;//进入事件Signal on_exit;//退出事件
};
int main(int argc, char *argv[])
{Input in;A a;in.on_input.connect([&a](int i){a.age = i;a.on_input(i);});in.on_exit.connect([&a](int i){a.on_exit();});in.fun(4);//4return 0;
}
5:为了信号更加通用,使用变长模板参数来实现。注意:…在左边表示定义,在右边表示使用
#include <iostream>
#include <functional>
#include <vector>
template<class ...T>//定义T
struct Signal
{std::vector<std::function<void(T...)>> m_callbacks;//使用Tvoid connect(std::function<void(T...)> callback){m_callbacks.push_back(std::move(callback));}void emit(T... t){//使用T, 定义tfor(auto&& callback: m_callbacks){callback(t...);//使用t}}
};
struct A
{ void on_input(int i) const {std::cout<<"input "<<age<<std::endl;}void on_exit() const {std::cout<<"exit "<<std::endl;}int age = 32;
};
struct Input
{//调用该函数就发出 进入事件和退出事件的信号void fun(int i){on_input.emit(i);on_exit.emit();}Signal<int> on_input;//进入事件Signal<> on_exit;//退出事件
};
int main(int argc, char *argv[])
{Input in;A a;in.on_input.connect([&a](int i){a.age = i;a.on_input(i);});in.on_exit.connect([&a](){a.on_exit();});in.fun(4);//4return 0;
}
6:上述代码main函数中connect时传入的lambda表达式,下面在Signal中将其封装为bind方法,并提供对应的connect函数,使其更类似于Qt信号的connect。实际上Qt中是使用字符串来查找匹配的类型名,而这里我们使用模板更加高效
#include <iostream>
#include <functional>
#include <vector>template<class Self, class MemFn>
auto bind(Self *self, MemFn memfn){//第2个参数为成员函数指针:void(A::*)(int i),这里使用模板来避免写成该复杂类型//调用成员函数指针(a->*memfn)()。如果是普通函数指针,就是(*memfn)()这样调用return [self, memfn](auto... t){(self->*memfn)(t...);};
}template<class ...T>//定义T
struct Signal
{std::vector<std::function<void(T...)>> m_callbacks;//使用Tvoid connect(std::function<void(T...)> callback){m_callbacks.push_back(std::move(callback));}//提供一个bind版本的connect,类似qt语法template<class Self, class MemFn>void connect(Self *self, MemFn memfn){m_callbacks.push_back(bind(self, memfn));}void emit(T... t){//使用T, 定义tfor(auto&& callback: m_callbacks){callback(t...);//使用t}}
};
struct A
{ void on_input(int i){age = i;std::cout<<"input "<<age<<std::endl;}void on_exit(std::string s) const {std::cout<<"exit "<<s<<std::endl;}int age = 32;
};
struct Input
{//调用该函数就发出 进入事件和退出事件的信号void fun(int i){on_input.emit(i);on_exit.emit("byebye");}Signal<int> on_input;//进入事件Signal<std::string> on_exit;//退出事件
};int main(int argc, char *argv[])
{Input in;A a;in.on_input.connect(&a, &A::on_input);in.on_exit.connect(&a, &A::on_exit);in.fun(4);//4return 0;
}
下面写了重载函数test_fun作为要connect的函数,此时必须写明要使用哪个函数,因此下面使用static_cast进行转换
void test_fun(int m){std::cout<<"int "<<m<<std::endl;
}
void test_fun(std::string m){std::cout<<"string "<<m<<std::endl;
}
//function要求必须有唯一的重载,这样必须指定使用哪个
in.on_input.connect(static_cast<void(*)(int)>(test_fun));
7:为了避免connect的对象提前析构,下面代码使用智能指针
template<class Self, class MemFn>
auto bind(Self self, MemFn memfn){return [self, memfn](auto... t){((*self).*memfn)(t...);};
}void test2(Input &input){auto a = std::make_shared<A>();//使用智能指针而不是A a,避免对象提前析构input.on_input.connect(a, &A::on_input);//这里智能指针a发生拷贝input.on_exit.connect(a, &A::on_exit);
}int main(int argc, char *argv[])
{Input in;test2(in);in.fun(3);return 0;
}
如果是connect lambda表达式,注意按值捕获,否则智能指针和a对象都会提前析构掉
void test2(Input &input){auto a = std::make_shared<A>();input.on_input.connect([a](int i){//注意按值捕获areturn a->on_input(i);});
}
相关文章:
使用c++函数式编程实现Qt信号槽机制
问题背景 在下面的代码中,Input输入器 输入数据,希望A和B 接收数据。但使用的赋值,导致in.a和a只是拷贝数据,而不是同一个对象,使得数据不同步。 #include <iostream> struct A {int age 32; }; struct B {int …...
【Android】Activity子类之间的区别
从底层往顶层的继承顺序依次是: Activity,最原始的Activity androidx.core.app.ComponentActivity,仅仅优化了一个关于KeyEvent的拦截问题,一般不继承这个类 androidx.activity.ComponentActivity,支持和Android Arc…...
在 Mac 上使用 MLX 微调微软 phi3 模型
微调大语言模型是常见的需求,由于模型参数量大,即使用 Lora/Qlora 进行微调也需要 GPU 显卡,Mac M系是苹果自己的 GPU,目前主流的框架还在建立在 CUDA 的显卡架构,也就是主要的卡还是来自英伟达。如果要用 Mac 来做训练…...
【JavaEE】多线程代码案例(2)
🎏🎏🎏个人主页🎏🎏🎏 🎏🎏🎏JavaEE专栏🎏🎏🎏 🎏🎏🎏上一篇文章:多线程代码案例(1)&a…...
Halcon支持向量机
一 支持向量机 1 支持向量机介绍: 支持向量机(Support Vector Machine,SVM)是Corinna Cortes和Vapnik于1995年首先提出的,它在解决小样本、非线性及高维模式识别表现出许多特有的优势。 2 支持向量机原理: 在n维空间中找到一个分类超平面…...
【Python机器学习】模型评估与改进——在模型选择中使用评估指标
我们通常希望,在使用GridSearchCV或cross_val_score进行模型选择时能够使用AUC等指标。scikit-learn提供了一种非常简单的实现方法,那就是scoring参数,它可以同时用于GridSearchCV和cross_val_score。你只需要提供一个字符串,用于…...
【C语言】union 关键字
在C语言中,union关键字用于定义联合体。联合体是一种特殊的数据结构,它允许不同的数据类型共享同一段内存。所有联合体成员共享同一个内存位置,因此联合体的大小取决于其最大成员的大小。 定义和使用联合体 基本定义 定义一个联合体类型时…...
电脑回收站删除的文件怎么恢复?5个恢复方法详解汇总!
电脑回收站删除的文件怎么恢复?在我们日常使用电脑的过程中,难免会遇到误删文件的情况。一旦发现自己误删文件了,先不要着急,还是有很多方法可以找回的。市面上还是有很多好用的文件恢复软件可以使用,具体介绍如下。 本…...
mac 安装cnpm 淘宝镜像记录
mac 安装cnpm 淘宝镜像记录 本文介绍了在安装cnpm时遇到权限问题的解决方案,包括使用sudo,处理SSL证书过期,以及因版本不一致导致的错误处理方法,步骤包括设置npm配置、卸载和重新安装cnpm到特定版本。 安装 npm install cnpm …...
ArcGIS Pro SDK (七)编辑 11 撤销重做
ArcGIS Pro SDK (七)编辑 11 撤销&重做 文章目录 ArcGIS Pro SDK (七)编辑 11 撤销&重做1 撤消/重做最近的操作 环境:Visual Studio 2022 .NET6 ArcGIS Pro SDK 3.0 1 撤消/重做最近的操作 //撤销 if (MapV…...
Excel 中的元素定位:相对定位、绝对定位和混合定位
在Excel中,单元格引用有三种主要类型:相对定位、绝对定位和混合定位。 这些类型主要用于公式和函数中,决定在复制或拖动公式时引用如何变化。 1. 相对定位 相对定位指的是不带“$”符号的单元格引用,例如 A1。 这种引用方式在…...
Idea2024安装后点击无响应
问题 最近因工作需要,升级一下 idea 版本,之前一直使用的是2020版本,下载最新的2024版本(下载的 zip 包免安装模式,之前使用的2020版本也是免安装的,因为是免安装的,所以之前的版本也没有删除&…...
如何提高实验室分析结果的准确性呢
要提高实验室分析结果的准确性,可以从以下几个方面着手: 1、选择合适的实验方法 不同的实验方法具有不同的优缺点,实验方法的准确度直接影响测定结果的准确度。因此,在选择实验方法时,需要根据实验目的、实验原理、实…...
Perl 格式化输出:提升代码可读性的技巧
引言 Perl 是一种功能强大的脚本语言,广泛用于文本处理、系统管理、网络编程等多个领域。在 Perl 编程中,代码的格式化输出不仅有助于提升代码的可读性,还能增强程序的用户体验。本文将详细介绍如何在 Perl 中实现代码的格式化输出。 Perl …...
JavaScript基础-函数(完整版)
文章目录 函数基本使用函数提升函数参数arguments对象(了解)剩余参数(重点)展开运算符(...) 逻辑中断函数参数-默认参数函数返回值-return作用域(scope)全局作用域局部作用域变量的访问原则垃圾回收机制闭包 匿名函数函数表达式立即执行函数 箭头函数箭头…...
AI开发者的新选择:Mojo编程语言
随着人工智能技术的迅猛发展,编程语言的选择在AI项目的成功中扮演着至关重要的角色。近年来,Mojo编程语言作为一种专为AI开发者设计的新兴语言,逐渐引起了广泛关注。本文将详细介绍Mojo编程语言的特点、优势及其在AI开发中的应用。 目录 Mo…...
软考(高项)系统分析师--论软件开发模型及应用
文章目录 前言一、前期准备:二、论文部分: 前言 本文对系统分析师,软件开发模型及其应用文章进行展示,可以拷贝后直接粘贴到word 文档中。 一、前期准备: 项目主体功能项目背景常用的软件开发模型:瀑布模型ÿ…...
同一天提档又撤档!电影《野孩子》宣布取消7月10日公映安排——浔川电影报
同一天提档又撤档! 7月3日晚上10点,电影野孩子 发声明官宣撤档,“由于后期进度原因,电影《野孩子》将取消7月10日的公映安排,我们向各影管院线的同仁及所有观众朋友们致以最诚挚的歉意,谢谢大家这段时间的…...
Shell编程之免交互
一、Here Document免交互 1:概述 Here Document 是一个特殊用途的代码块,它在 Linux Shell 中使用 I/O 重定向的方式将命令列表提供给交互式程序或命令,比如 ftp、cat 或 read 命令,Here Document 是标准输入的一种替代品 语法…...
基于opencv的斜光测距及python实现
1.前言 最近做了一个基于opencv的斜光测距的小项目,东西不多,但是很有意思,值得拿出来学一学。项目里面需要比较精确的定位功能,将前人matlab代码移植到python上,并且做了一些优化,简化逻辑(毕竟我是专业的…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
