使用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上,并且做了一些优化,简化逻辑(毕竟我是专业的…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...

C# winform教程(二)----checkbox
一、作用 提供一个用户选择或者不选的状态,这是一个可以多选的控件。 二、属性 其实功能大差不差,除了特殊的几个外,与button基本相同,所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...