0

0

C++如何实现基于可变参数模板的信号与槽系统?(事件驱动机制)

冰火之心

冰火之心

发布时间:2026-03-04 12:03:11

|

649人浏览过

|

来源于php中文网

原创

因std::function会丢失编译期类型检查,导致信号-槽签名不匹配时无法在编译期报错;必须用模板推导+虚基类带参call实现类型安全擦除,并配合weak_ptr防悬空、参数包转发避免tuple拷贝。

c++如何实现基于可变参数模板的信号与槽系统?(事件驱动机制)

为什么不用 std::function + std::vector 直接存槽函数?

因为可变参数模板的核心价值不是“能传参”,而是让连接时的类型检查发生在编译期——connect(&obj, &Obj::method, &receiver, &Receiver::onData) 这种写法,如果签名不匹配(比如 onData(int, std::string) 但信号发的是 int),编译器立刻报错,而不是运行时崩溃或静默丢弃。

std::function<void></void> 装所有槽,等于主动放弃类型安全;而靠 void* + 手动 cast 更危险,容易误调用或栈破坏。

实操建议:

Keeva AI
Keeva AI

AI一键生成数字人营销视频

下载
  • 每个信号类模板参数必须包含完整函数签名,例如 Signal<void const char></void>
  • 槽函数注册时,用 std::bind 或 lambda 封装成匹配签名的可调用对象,再通过 std::function 存储——但封装动作必须在 connect 内完成,不能暴露裸 std::function 接口给用户
  • 禁止把 std::function 当信号基类成员直接存;应为每组参数组合生成独立特化类,靠模板推导保证调用一致性

connect 怎么做类型擦除又不丢签名信息?

关键在两层包装:外层用模板函数接收任意可调用体并推导其参数,内层用类型擦除容器(如 std::unique_ptr 指向虚基类)存具体调用逻辑,但虚基类接口本身带模板参数约束。

立即学习C++免费学习笔记(深入)”;

常见错误现象:写一个通用 SlotBase,只留 virtual void call() = 0,结果所有槽都变成无参调用,信号发来的参数全丢了。

实操建议:

  • 定义 template<typename... args> struct SlotBase { virtual void call(Args&&... args) = 0; };</typename...> —— 注意,call 必须带参数包,且是转发引用
  • 特化实现类 SlotImpl<functor args...></functor> 继承它,内部保存 Functor 并在 call 中完美转发
  • connect 函数模板里用 decltypestd::is_invocable_v 静态断言 Functor 是否可被信号参数调用,不满足直接编译失败

如何避免重复触发和悬空指针?

信号触发时若槽函数内部又调用 disconnect 或析构了 receiver,后续槽列表遍历就可能访问已释放内存。这不是多线程问题,单线程也会出事。

使用场景:GUI 中按钮点击触发业务逻辑,逻辑中途删掉自己所在的窗口对象,然后信号继续往后调用其他槽——第二个槽拿到的就是野指针。

实操建议:

  • 槽容器不存裸指针,改用 std::weak_ptr 包裹 receiver 对象(要求 receiver 继承自 std::enable_shared_from_this
  • 触发前先对每个 weak_ptr 调用 lock(),返回空则跳过该槽,不崩溃也不报错
  • 禁止在槽函数里修改当前正在遍历的槽列表(如边调用边 disconnect);如需动态管理,改用“延迟删除”队列,在本次信号结束之后统一清理

为什么不能直接用 std::tuple 存参数?

可以存,但会引入不必要的拷贝和生命周期管理负担。信号发射时参数通常是临时值或局部变量,std::tuple 默认按值存储,意味着每次 emit 都要构造 tuple、再逐个解包——对高频信号(如鼠标移动)就是性能黑洞。

性能影响:一次 emit<int std::string>(42, "hello")</int> 若走 tuple 路径,至少触发两次字符串拷贝(构造 tuple 一次,调用槽时解包再一次);而直接参数包展开,字符串可完美转发为右值引用,零拷贝。

实操建议:

  • 信号类内部不保存参数,emit 是纯转发函数:template<typename... args> void emit(Args&&... args) { for (auto& slot : slots_) slot->call(std::forward<args>(args)...); }</args></typename...>
  • 如果真需要延迟发射(如 post 到事件循环),才考虑用 std::make_tuple(std::forward<args>(args)...)</args> + std::apply,但这是例外路径,不是默认设计
  • 别为了“看起来统一”强行把即时调用和延迟调用塞进同一套 tuple 存储逻辑里——它们的优化目标根本不同

最易被忽略的一点:信号对象本身的生命周期管理。它通常作为成员变量挂在 sender 上,但 sender 析构顺序不确定,如果 sender 成员中还有其他依赖该信号的对象(比如某个策略类持有了 connect 返回的 connection handle),析构顺序错位就会导致未定义行为。这类问题不会报错,只会偶发 crash,调试成本极高。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

930

2023.08.02

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

698

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

219

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1561

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

645

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1128

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

1102

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

187

2025.07.29

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

4

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 10.6万人学习

C 教程
C 教程

共75课时 | 5.2万人学习

C++教程
C++教程

共115课时 | 20.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号