使用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上,并且做了一些优化,简化逻辑(毕竟我是专业的…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
