当前位置: 首页 > news >正文

使用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信号槽机制

问题背景 在下面的代码中&#xff0c;Input输入器 输入数据&#xff0c;希望A和B 接收数据。但使用的赋值&#xff0c;导致in.a和a只是拷贝数据&#xff0c;而不是同一个对象&#xff0c;使得数据不同步。 #include <iostream> struct A {int age 32; }; struct B {int …...

【Android】Activity子类之间的区别

从底层往顶层的继承顺序依次是&#xff1a; Activity&#xff0c;最原始的Activity androidx.core.app.ComponentActivity&#xff0c;仅仅优化了一个关于KeyEvent的拦截问题&#xff0c;一般不继承这个类 androidx.activity.ComponentActivity&#xff0c;支持和Android Arc…...

在 Mac 上使用 MLX 微调微软 phi3 模型

微调大语言模型是常见的需求&#xff0c;由于模型参数量大&#xff0c;即使用 Lora/Qlora 进行微调也需要 GPU 显卡&#xff0c;Mac M系是苹果自己的 GPU&#xff0c;目前主流的框架还在建立在 CUDA 的显卡架构&#xff0c;也就是主要的卡还是来自英伟达。如果要用 Mac 来做训练…...

【JavaEE】多线程代码案例(2)

&#x1f38f;&#x1f38f;&#x1f38f;个人主页&#x1f38f;&#x1f38f;&#x1f38f; &#x1f38f;&#x1f38f;&#x1f38f;JavaEE专栏&#x1f38f;&#x1f38f;&#x1f38f; &#x1f38f;&#x1f38f;&#x1f38f;上一篇文章&#xff1a;多线程代码案例(1)&a…...

Halcon支持向量机

一 支持向量机 1 支持向量机介绍&#xff1a; 支持向量机(Support Vector Machine&#xff0c;SVM)是Corinna Cortes和Vapnik于1995年首先提出的&#xff0c;它在解决小样本、非线性及高维模式识别表现出许多特有的优势。 2 支持向量机原理: 在n维空间中找到一个分类超平面…...

【Python机器学习】模型评估与改进——在模型选择中使用评估指标

我们通常希望&#xff0c;在使用GridSearchCV或cross_val_score进行模型选择时能够使用AUC等指标。scikit-learn提供了一种非常简单的实现方法&#xff0c;那就是scoring参数&#xff0c;它可以同时用于GridSearchCV和cross_val_score。你只需要提供一个字符串&#xff0c;用于…...

【C语言】union 关键字

在C语言中&#xff0c;union关键字用于定义联合体。联合体是一种特殊的数据结构&#xff0c;它允许不同的数据类型共享同一段内存。所有联合体成员共享同一个内存位置&#xff0c;因此联合体的大小取决于其最大成员的大小。 定义和使用联合体 基本定义 定义一个联合体类型时…...

电脑回收站删除的文件怎么恢复?5个恢复方法详解汇总!

电脑回收站删除的文件怎么恢复&#xff1f;在我们日常使用电脑的过程中&#xff0c;难免会遇到误删文件的情况。一旦发现自己误删文件了&#xff0c;先不要着急&#xff0c;还是有很多方法可以找回的。市面上还是有很多好用的文件恢复软件可以使用&#xff0c;具体介绍如下。 本…...

mac 安装cnpm 淘宝镜像记录

mac 安装cnpm 淘宝镜像记录 本文介绍了在安装cnpm时遇到权限问题的解决方案&#xff0c;包括使用sudo&#xff0c;处理SSL证书过期&#xff0c;以及因版本不一致导致的错误处理方法&#xff0c;步骤包括设置npm配置、卸载和重新安装cnpm到特定版本。 安装 npm install cnpm …...

ArcGIS Pro SDK (七)编辑 11 撤销重做

ArcGIS Pro SDK &#xff08;七&#xff09;编辑 11 撤销&重做 文章目录 ArcGIS Pro SDK &#xff08;七&#xff09;编辑 11 撤销&重做1 撤消/重做最近的操作 环境&#xff1a;Visual Studio 2022 .NET6 ArcGIS Pro SDK 3.0 1 撤消/重做最近的操作 //撤销 if (MapV…...

Excel 中的元素定位:相对定位、绝对定位和混合定位

在Excel中&#xff0c;单元格引用有三种主要类型&#xff1a;相对定位、绝对定位和混合定位。 这些类型主要用于公式和函数中&#xff0c;决定在复制或拖动公式时引用如何变化。 1. 相对定位 相对定位指的是不带“$”符号的单元格引用&#xff0c;例如 A1。 这种引用方式在…...

Idea2024安装后点击无响应

问题 最近因工作需要&#xff0c;升级一下 idea 版本&#xff0c;之前一直使用的是2020版本&#xff0c;下载最新的2024版本&#xff08;下载的 zip 包免安装模式&#xff0c;之前使用的2020版本也是免安装的&#xff0c;因为是免安装的&#xff0c;所以之前的版本也没有删除&…...

如何提高实验室分析结果的准确性呢

要提高实验室分析结果的准确性&#xff0c;可以从以下几个方面着手&#xff1a; 1、选择合适的实验方法 不同的实验方法具有不同的优缺点&#xff0c;实验方法的准确度直接影响测定结果的准确度。因此&#xff0c;在选择实验方法时&#xff0c;需要根据实验目的、实验原理、实…...

Perl 格式化输出:提升代码可读性的技巧

引言 Perl 是一种功能强大的脚本语言&#xff0c;广泛用于文本处理、系统管理、网络编程等多个领域。在 Perl 编程中&#xff0c;代码的格式化输出不仅有助于提升代码的可读性&#xff0c;还能增强程序的用户体验。本文将详细介绍如何在 Perl 中实现代码的格式化输出。 Perl …...

JavaScript基础-函数(完整版)

文章目录 函数基本使用函数提升函数参数arguments对象&#xff08;了解&#xff09;剩余参数(重点)展开运算符(...) 逻辑中断函数参数-默认参数函数返回值-return作用域(scope)全局作用域局部作用域变量的访问原则垃圾回收机制闭包 匿名函数函数表达式立即执行函数 箭头函数箭头…...

AI开发者的新选择:Mojo编程语言

随着人工智能技术的迅猛发展&#xff0c;编程语言的选择在AI项目的成功中扮演着至关重要的角色。近年来&#xff0c;Mojo编程语言作为一种专为AI开发者设计的新兴语言&#xff0c;逐渐引起了广泛关注。本文将详细介绍Mojo编程语言的特点、优势及其在AI开发中的应用。 目录 Mo…...

软考(高项)系统分析师--论软件开发模型及应用

文章目录 前言一、前期准备&#xff1a;二、论文部分: 前言 本文对系统分析师&#xff0c;软件开发模型及其应用文章进行展示&#xff0c;可以拷贝后直接粘贴到word 文档中。 一、前期准备&#xff1a; 项目主体功能项目背景常用的软件开发模型&#xff1a;瀑布模型&#xff…...

同一天提档又撤档!电影《野孩子》宣布取消7月10日公映安排——浔川电影报

同一天提档又撤档&#xff01; 7月3日晚上10点&#xff0c;电影野孩子 发声明官宣撤档&#xff0c;“由于后期进度原因&#xff0c;电影《野孩子》将取消7月10日的公映安排&#xff0c;我们向各影管院线的同仁及所有观众朋友们致以最诚挚的歉意&#xff0c;谢谢大家这段时间的…...

Shell编程之免交互

一、Here Document免交互 1&#xff1a;概述 Here Document 是一个特殊用途的代码块&#xff0c;它在 Linux Shell 中使用 I/O 重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp、cat 或 read 命令&#xff0c;Here Document 是标准输入的一种替代品 语法…...

基于opencv的斜光测距及python实现

1.前言 最近做了一个基于opencv的斜光测距的小项目&#xff0c;东西不多&#xff0c;但是很有意思&#xff0c;值得拿出来学一学。项目里面需要比较精确的定位功能&#xff0c;将前人matlab代码移植到python上&#xff0c;并且做了一些优化&#xff0c;简化逻辑(毕竟我是专业的…...

梯度下降算法

占楼&#xff0c;明天写...

第5章:软件工程

第5章&#xff1a;软件工程 软件工程概述 软件生命周期 软件过程 1.能力成熟度模型(CMM) CMM&#xff08;能力成熟度模型&#xff09;是一个评估和确定组织软件过程成熟度的模型。它最早于1987年由美国国防部软件工程研究所&#xff08;SEI&#xff09;提出&#xff0c;其目的…...

cefsharp在splitContainer.Panel2中显示调试工具DevTools(非弹出式)含源代码

一、弹出式调试工具 (ShowDevTools) ChromiumWebBrowser webbrowser; public void showDevTools(){//定位到某元素webbrowser.ShowDevTools(null, parameters.XCoord, parameters.YCoord);...

nginx部署多个项目;vue打包项目部署设置子路径访问;一个根域名(端口)配置多个子项目

本文解决&#xff1a; vue打包项目部署设置子路径访问&#xff1b;nginx部署多个子项目&#xff1b;一个ip/域名 端口 配置多个子项目&#xff1b;配置后&#xff0c;项目能访问&#xff0c;但是刷新页面就丢失的问题 注&#xff1a;本文需要nginx配置基础。基础不牢的可见文…...

02-部署LVS-DR群集

1.LVS-DR工作原理 LVS-DR模式&#xff0c;Director Server作为群集的访问入口&#xff0c;不作为网购使用&#xff0c;节点Director Server 与 Real Server 需要在同一个网络中&#xff0c;返回给客户端的数据不需要经过Director Server 为了响应对整个群集的访问&#xff0c;…...

DataWhale-吃瓜教程学习笔记 (六)

学习视频**&#xff1a;第4章-决策树_哔哩哔哩_bilibili 西瓜书对应章节&#xff1a; 第五章 5.1&#xff1b;5.2&#xff1b;5.3 文章目录 MP 神经元- 感知机模型 &#xff08;分类模型&#xff09;-- 损失函数定义--- 感知机学习算法 - 随机梯度下降法 - 神经网络需要解决的问…...

在docker配置Nginx环境配置

应用于商业模式集中&#xff0c;对于各种API的调用&#xff0c;对于我们想要的功能进行暴露&#xff0c;对于不用的进行拦截进行鉴权。用于后面的付费 开发环境 正式上线模式 一、常用命令 停止&#xff1a;docker stop Nginx重启&#xff1a;docker restart Nginx删除服务&a…...

在不修改.gitignore的情况下,忽略个人文件的提交

Git提供了一个assume-unchanged命令&#xff0c;可以将文件标记为“假设未更改”。这意味着Git将忽略该文件的更改&#xff0c;不会将其提交到仓库中。要使用该命令&#xff0c;只需运行以下命令&#xff1a; git update-index --assume-unchanged <file>其中&#xff0…...

【Unity navmeshaggent 组件】

【Unity navmeshaggent 组件】 组件概述&#xff1a; NavMeshAgent是Unity AI系统中的一个组件&#xff0c;它允许游戏对象&#xff08;通常是一个角色或AI&#xff09;在导航网格&#xff08;NavMesh&#xff09;上自动寻路。 组件属性&#xff1a; Radius&#xff1a;导航…...

51单片机第18步_将TIM0用作13位定时器

本章重点学习将TIM0用作13位定时器。 1、定时器0工作在模式0框图 2、定时器0工作在模式0举例 1、Keil C51中有一些关键字&#xff0c;需要牢记&#xff1a; interrupt 0&#xff1a;指定当前函数为外部中断0&#xff1b; interrupt 1&#xff1a;指定当前函数为定时器0中断…...