讲师中心 微信公众号
AI工具推荐 视频效率加速

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

夏芳酱_1928

夏芳酱_1928

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

|

669人浏览过

|

来源于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 更危险,容易误调用或栈破坏。

实操建议:

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
  • 每个信号类模板参数必须包含完整函数签名,例如 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 函数模板里用 decltype 和 std::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,调试成本极高。

相关文章

驱动精灵
驱动精灵

驱动精灵基于驱动之家十余年的专业数据积累,驱动支持度高,已经为数亿用户解决了各种电脑驱动问题、系统故障,是目前有效的驱动软件,有需要的小伙伴快来保存下载体验吧!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

相关专题

更多
string转int
string转int

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

6859

2023.08.02

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

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

1818

2023.08.03

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

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

2904

2023.09.04

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

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

6204

2023.10.24

字符串介绍
字符串介绍

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

6049

2023.11.24

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

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

7654

2024.03.22

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

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

11494

2024.04.29

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

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

5919

2025.07.29

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn