原生C++实现信号与槽机制:原理详解

信号与槽机制是一种广泛应用于事件驱动系统和GUI框架(如Qt)的设计模式。它允许组件之间通过订阅-发布模式进行通信,从而实现松耦合的设计。本文将详细讲解如何在原生C++中从零开始实现信号与槽机制,并深入探讨其工作原理。


一、信号与槽机制的核心概念

信号与槽机制的核心思想是:一个组件(信号)可以发出某种事件,其他组件(槽)可以订阅该事件,并在事件触发时执行相应的操作。这种设计模式的优势在于:

  • 松耦合:信号和槽之间没有直接的依赖关系,提高了系统的灵活性和可维护性。
  • 可扩展性:可以轻松地添加新的槽函数,而无需修改信号的代码。
  • 事件驱动:适用于需要异步处理事件的场景。

二、实现信号与槽机制的步骤

1. 定义Signal类

首先,我们需要定义一个Signal类,用于管理槽函数的连接和触发。

#include <vector>
#include <functional>
#include <mutex>template<typename... Args>
class Signal {
public:// 连接槽函数void connect(std::function<void(Args...)> slot) {std::lock_guard<std::mutex> lock(mutex_);slots.push_back(slot);}// 断开槽函数void disconnect(std::function<void(Args...)> slot) {std::lock_guard<std::mutex> lock(mutex_);auto it = std::remove(slots.begin(), slots.end(), slot);slots.erase(it, slots.end());}// 触发信号void emit(Args... args) {std::lock_guard<std::mutex> lock(mutex_);for (const auto& slot : slots) {slot(args...);}}private:std::vector<std::function<void(Args...)>> slots;std::mutex mutex_;
};

关键点解释

  • 模板参数Args :使用模板参数Args,使得Signal类可以支持不同类型的信号和槽函数。Args...表示可变参数模板,支持任意数量和类型的参数。
  • connect方法:用于将槽函数连接到信号上。槽函数被存储在一个std::vector<std::function<void(Args...)>>容器中。
  • disconnect方法:用于断开已连接的槽函数。通过std::removeerase操作,从容器中移除指定的槽函数。
  • emit方法:用于触发信号,并将参数传递给所有连接的槽函数。遍历容器中的所有槽函数,并依次调用它们。
  • 线程安全性:使用std::mutexstd::lock_guard,确保在多线程环境下对槽函数容器的操作是线程安全的。

2. 定义槽函数

槽函数是响应信号触发的操作。以下是两个简单的槽函数示例:

#include <iostream>void slot1() {std::cout << "Slot 1 triggered!" << std::endl;
}void slot2(int arg) {std::cout << "Slot 2 received argument: " << arg << std::endl;
}

说明

  • 槽函数可以是任何接受特定参数并执行相应操作的函数或lambda表达式。
  • 槽函数的类型必须与信号的模板参数匹配。

3. 使用Signal类

main函数中,我们可以创建一个Signal对象,并连接槽函数。

int main() {// 创建一个Signal对象,用于发送通知Signal<std::string> notificationSignal;// 连接槽函数notificationSignal.connect(onNotificationReceived);// 发送通知notificationSignal.emit("Hello, YongYong! This is a notification from Guoyao.");return 0;
}

说明

  • Signal<std::string>表示该信号接受一个字符串参数。
  • connect方法将槽函数onNotificationReceived连接到信号上。
  • emit方法触发信号,并将通知消息传递给所有连接的槽函数。

三、实现原理的深入分析

1. 模板与参数化

Signal类使用模板参数Args,使得它可以支持不同类型的信号和槽函数。这种参数化设计使得信号与槽机制具有高度的灵活性和可扩展性。

template<typename... Args>
class Signal {// ...
};

通过模板参数,Signal类可以处理任意数量和类型的参数。例如:

  • Signal<void>:不带参数的信号。
  • Signal<int>:带一个整数参数的信号。
  • Signal<int, double>:带两个参数(整数和浮点数)的信号。

2. 槽函数的存储与管理

槽函数被存储在一个std::vector<std::function<void(Args...)>>容器中。std::function是一个通用的函数包装器,可以存储任何可调用对象(如函数指针、lambda表达式等)。

std::vector<std::function<void(Args...)>> slots;

槽函数的连接与断开

  • connect方法将槽函数添加到容器中。
  • disconnect方法从容器中移除指定的槽函数。

注意事项

  • 由于std::function的比较操作可能不准确,断开槽函数时需要确保传递的槽函数与连接时完全相同。
  • 如果使用lambda表达式作为槽函数,可能会遇到比较操作的问题,因为lambda表达式是匿名的,无法直接比较。

3. 信号的触发

emit方法负责触发信号,并将参数传递给所有连接的槽函数。

void emit(Args... args) {std::lock_guard<std::mutex> lock(mutex_);for (const auto& slot : slots) {slot(args...);}
}

关键点

  • 使用std::lock_guard确保在多线程环境下对槽函数容器的访问是线程安全的。
  • 遍历容器中的所有槽函数,并依次调用它们,传递参数。

4. 线程安全性

在多线程环境下,信号与槽机制需要确保对槽函数容器的操作是线程安全的。Signal类通过使用std::mutexstd::lock_guard实现了这一点。

std::mutex mutex_;

线程安全操作

  • connectdisconnectemit方法中,使用std::lock_guardmutex_进行加锁,确保同一时间只有一个线程可以操作槽函数容器。

四、实际应用示例

场景描述

假设我们有一个“国遥”模块需要向“勇勇(YongYong)”发送通知。我们可以使用信号与槽机制来实现这一功能。

实现代码

以下是完整的代码示例:

#include <vector>
#include <functional>
#include <mutex>
#include <iostream>
#include <string>template<typename... Args>
class Signal {
public:// 连接槽函数void connect(std::function<void(Args...)> slot) {std::lock_guard<std::mutex> lock(mutex_);slots.push_back(slot);}// 断开槽函数void disconnect(std::function<void(Args...)> slot) {std::lock_guard<std::mutex> lock(mutex_);auto it = std::remove(slots.begin(), slots.end(), slot);slots.erase(it, slots.end());}// 触发信号void emit(Args... args) {std::lock_guard<std::mutex> lock(mutex_);for (const auto& slot : slots) {slot(args...);}}private:std::vector<std::function<void(Args...)>> slots;std::mutex mutex_;
};// 定义槽函数
void onNotificationReceived(const std::string& message) {std::cout << "Notification received by YongYong: " << message << std::endl;
}int main() {// 创建一个Signal对象,用于发送通知Signal<std::string> notificationSignal;// 连接YongYong的槽函数notificationSignal.connect(onNotificationReceived);// 发送通知notificationSignal.emit("Hello, YongYong! This is a notification from Guoyao.");return 0;
}

运行结果

运行上述代码后,控制台将输出:

Notification received by YongYong: Hello, YongYong! This is a notification from Guoyao.

这表明“国遥”成功地向“勇勇(YongYong)”发送了通知,且通知被正确接收和处理。


五、总结与扩展

通过以上步骤,我们成功地实现了“国遥向勇勇(YongYong)发送通知”的信号与槽机制。这一机制不仅提高了代码的模块化和可维护性,还使得系统中的不同模块之间能够高效、灵活地进行通信。

在实际项目中,可以根据具体需求进一步扩展和优化这一机制,例如:

  • 支持弱引用:防止槽函数持有对象而导致的内存泄漏。可以通过std::weak_ptr来实现。
  • 信号继承与重载:允许信号被继承和重载,以支持更复杂的事件处理逻辑。
  • 信号过滤:在触发信号时,根据某些条件过滤槽函数,避免不必要的调用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【人工智能】OpenAI的AI代理革命:通向超拟人交互的未来之路

人工智能代理(AI Agent)正引领一场深刻的技术变革,其核心在于赋予AI系统感知、规划、行动和学习的能力,以自主完成复杂任务。OpenAI作为这一领域的先驱,通过其大型语言模型(LLMs)如GPT系列,极大地推动了AI代理的发展,使其在自然语言理解、生成和工具使用方面达到了前所…

Ubuntu虚拟机介绍、命令、安装软件指令(一)

Ubuntu介绍 Ubuntu 是一个基于 Debian 的开源 Linux 操作系统&#xff0c;由 Canonical 公司赞助开发。它是目前全球最流行的 Linux 发行版之一&#xff0c;以其用户友好性、稳定性和强大的社区支持著称。 核心特性 1.免费开源 完全免费使用和分发&#xff0c;遵循 GNU GPL…

企业微信服务商创建第三方应用配置数据回调url和指令回调url的java代码实现

关键区别说明&#xff08;指令回调 vs 数据回调&#xff09;特性指令回调数据回调触发场景授权/取消授权等管理事件通讯录变更、应用菜单点击等业务事件关键字段InfoTypeEvent ChangeType典型事件suite_auth, cancel_authchange_contact, suite_ticket响应要求必须返回加密的&…

LazyLLM教程 | 第2讲:10分钟上手一个最小可用RAG系统

贴心小梗概本文将介绍使用LazyLLM搭建最基础的RAG的流程。首先介绍使用LazyLLM搭建RAG系统的必要环境配置&#xff0c;然后简单回顾RAG的基本流程&#xff0c;接下来分别介绍RAG中文档加载、检索组件、生成组件三个关键部分的参数和基本使用方法&#xff0c;最后利用LazyLLM实现…

android9-PMS-常见问题及分析步骤

以下是基于 Android 9 的 Package Manager Service (PMS) 常见问题及分析步骤&#xff0c;结合系统原理与优化实践整理&#xff1a; &#x1f527; 一、开机性能问题 现象 开机时间随应用增多显著延长&#xff0c;卡在“正在启动应用”阶段。 分析步骤 ① 确认扫描阶段耗时adb…

生成模型实战 | GLOW详解与实现

生成模型实战 | GLOW详解与实现0. 前言1. 归一化流模型1.1 归一化流与变换公式1.2 RealNVP 的通道翻转2. GLOW 架构2.1 ActNorm2.2 可逆 11 卷积2.3 仿射耦合层2.4 多尺度架构3. 使用 PyTorch 实现 GLOW3.1 数据处理3.2 模型构建3.3 模型训练0. 前言 GLOW (Generative Flow) 是…

行业案例:杰和科技为智慧教育构建数字化硬件底座

清晨8点10分&#xff0c;深圳某学生踏入校园&#xff0c;智慧门闸识别身份&#xff0c;并同步发给家长&#xff1b;走廊里的“智慧班牌”向他们展示今日的课表&#xff1b;课堂上&#xff0c;教室前方的多媒体播放器里&#xff0c;老师引导学生学习“居民楼消防隐患”知识&…

Redis与MySQL数据同步:从“双写一致性”到实战方案

Redis与MySQL数据同步&#xff1a;从“双写一致性”到实战方案 在分布式系统中&#xff0c;Redis作为高性能缓存被广泛使用——它能将热点数据从MySQL中“搬运”到内存&#xff0c;大幅降低数据库压力、提升接口响应速度。但随之而来的核心问题是&#xff1a;当MySQL数据更新时…

Java源码构建智能名片小程序

在移动互联网时代&#xff0c;纸质名片的局限性日益凸显——信息更新不便、客户管理困难、营销效果难以追踪。智能电子名片小程序以其便捷、高效、智能的特点&#xff0c;正成为商务人士的"数字营销门户"。而基于Java技术栈开发的智能名片系统&#xff0c;凭借其稳定…

如何在短时间内显著提升3D效果图渲染速度?

在建筑设计、游戏开发、影视制作等行业&#xff0c;3D效果图的渲染速度是项目进度与效率的关键瓶颈。面对复杂场景时&#xff0c;漫长的渲染等待尤为突出。要在保证质量的前提下大幅缩短渲染时间&#xff0c;以下优化策略至关重要&#xff1a; 1. 升级硬件配置&#xff1a;渲染…

配置daemon.json使得 Docker 容器能够使用服务器GPU【验证成功】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 文章目录&#x1f50d;你遇到的错误&#xff1a;&#x1f50d; 根本原因✅ 解决方案&#xff1a;正确安装 NVIDIA Container Toolkit✅ 第一步&#xff1a;卸载旧版本&#xff08;如果存在&…

Linux 系统进程管理与计划任务详解

Linux 系统进程管理与计划任务详解 一、程序与进程的基本概念 程序&#xff1a;保存在外部存储介质中的可执行机器代码和数据的静态集合。进程&#xff1a;在CPU及内存中处于动态执行状态的计算机程序。关系&#xff1a;每个程序启动后&#xff0c;可创建一个或多个进程。 二、…

【图像处理】直方图均衡化c++实现

直方图均衡化是一种通过调整图像像素灰度值分布&#xff0c;来增强图像对比度的经典数字图像处理技术。其核心在于将原始图像的灰度直方图从集中的某个区间“拉伸”或“均衡”到更广泛的区间&#xff0c;让图像的明暗细节更清晰&#xff0c;关键在于利用累积分布函数实现灰度值…

Web前端实战:Vue工程化+ElementPlus

1.Vue工程化 1.1介绍 模块化&#xff1a;将js和css等&#xff0c;做成一个个可复用模块组件化&#xff1a;我们将UI组件&#xff0c;css样式&#xff0c;js行为封装成一个个的组件&#xff0c;便于管理规范化&#xff1a;我们提供一套标准的规范的目录接口和编码规范&#xff0…

ECMAScript2021(ES12)新特性

概述 ECMAScript2021于2021年6月正式发布&#xff0c; 本文会介绍ECMAScript2021(ES12)&#xff0c;即ECMAScript的第12个版本的新特性。 以下摘自官网&#xff1a;ecma-262 ECMAScript 2021, the 12th edition, introduced the replaceAll method for Strings; Promise.any,…

Tlias 案例-整体布局(前端)

开发流程前端开发和后端开发是一样的&#xff0c;都需要阅读接口文档。 准备工作&#xff1a; 1&#xff1a;导入项目中准备的基础过程到 VsCode。2&#xff1a;启动前端项目&#xff0c;访问该项目3&#xff1a;熟悉一下基本的布局<script setup></script><tem…

三十二、【Linux网站服务器】搭建httpd服务器演示虚拟主机配置、网页重定向功能

httpd服务器功能演示一、虚拟主机配置虚拟主机技术全景虚拟主机目录规范1. 基于端口的虚拟主机&#xff08;8080/8081&#xff09;2. 基于IP的虚拟主机&#xff08;192.168.1.100/192.168.1.101&#xff09;3. 基于域名的虚拟主机&#xff08;site1.com/site2.com&#xff09;二…

串行化:MYSQL事务隔离级别中的终极防护

在现代应用程序中&#xff0c;数据的一致性和可靠性至关重要。想象一下&#xff0c;如果在一个银行系统中&#xff0c;两个用户同时试图转账到同一个账户&#xff0c;最终的数据结果可能会出乎意料。为了避免这种情况&#xff0c;MYSQL提供了不同的事务隔离级别&#xff0c;其中…

RAG:检索增强生成的范式演进、技术突破与前沿挑战

1 核心定义与原始论文 RAG&#xff08;Retrieval-Augmented Generation&#xff09;由Facebook AI Research团队于2020年提出&#xff0c;核心思想是将参数化记忆&#xff08;预训练语言模型&#xff09;与非参数化记忆&#xff08;外部知识库检索&#xff09;结合&#xff0c…

2024年蓝桥杯Scratch10月图形化stema选拔赛真题——旋转的图形

旋转的图形编程实现旋转的图形。具体要求1&#xff09;点击绿旗&#xff0c;在舞台上出现滑杆形式的变量 r&#xff0c;取值范围为-1、0、1&#xff0c;默认值为 0&#xff0c;如图所示&#xff1b;2&#xff09;1秒后&#xff0c;在舞台上绘制出一个红色正方形&#xff08;边长…