掌握C++回调:按值捕获、按引用捕获与弱引用
文章目录
- 一、按引用捕获和按值捕获
- 1.1 原理
- 1.2 案例
- 二、弱引用
- 2.1 原理
- 2.2 案例一
- 2.3 案例二:使用`base`库的弱引用
- 三、总结
在C++回调中,当使用Lambda表达式捕获外部变量时,有两种捕获方式:按值捕获和按引用捕获。
一、按引用捕获和按值捕获
1.1 原理
-
按引用捕获是将外部变量的引用存储在Lambda表达式的闭包中,
[&]
表示按引用捕获所有外部变量。这样,当Lambda表达式执行时,它将直接访问原始变量。这种方式在某些情况下可能导致问题,例如,当回调执行时,原始变量已经失效(例如,原始变量是栈上的局部变量,而回调在该变量离开作用域后执行)。 -
按值捕获是将外部变量的值复制到Lambda表达式的闭包中。这样,当Lambda表达式执行时,它将使用这个复制的值,而不是原始变量的值。这种方式可以避免在回调执行时,原始变量已经失效的问题。
1.2 案例
原理虽然很简单,但是当我们处于复杂的业务代码中时,仍然不免会写出bug。下面是笔者遇到的一个真实案例:
std::string WebProxyKeysHelper::GetAuthCode(std::string &st_or_code, std::string &last_key) {...auto ph = (st_or_code == KEY_TYPE_OF_ST_STR ? RefreshProxySt() : RefreshOauthCode());auto prom_ptr = std::make_shared<std::promise<std::string>>();std::future<std::string> fut_pb = prom_ptr->get_future();ph.then([&, prom_ptr](bool ret) {std::string tmp_key = "";if (ret) {tmp_key = st_or_code == KEY_TYPE_OF_ST_STR ? proxy_st_ : oauth_code_;UpdateKeys(st_or_code, tmp_key);Schedule();}prom_ptr->set_value(tmp_key);}).onError([&, prom_ptr](const std::exception& ex){prom_ptr->set_value("");});}...return current_key;}
在上述代码中,WebProxyKeysHelper::GetAuthCode
函数通过异步操作 ph
获取代理密钥。然后,根据异步操作的结果,回调函数更新密钥并设置 prom_ptr
的值。然而,这段代码存在一个潜在的问题,即在回调函数中使用了按引用捕获的 st_or_code
变量。
问题在于,当 ph.then([&, prom_ptr](bool ret) { ... })
回调执行时,st_or_code
变量可能已经离开了作用域并被销毁。这会导致程序偶现闪退,也可能导致数值异常,最终表现为业务逻辑异常,因为回调函数试图访问一个已经失效的栈变量。
修改的方式是,将 st_or_code
变量改为按值捕获。这样,在回调执行时,即使原始的 st_or_code
变量离开了作用域,回调中仍然可以安全地使用其复制的值。下面是修正后的代码:
std::string WebProxyKeysHelper::GetAuthCode(std::string &st_or_code, std::string &last_key) {...auto ph = (st_or_code == KEY_TYPE_OF_ST_STR ? RefreshProxySt() : RefreshOauthCode());auto prom_ptr = std::make_shared<std::promise<std::string>>();std::future<std::string> fut_pb = prom_ptr->get_future();ph.then([&, st_or_code, prom_ptr](bool ret) { // 注意这里改为按值捕获 st_or_codestd::string tmp_key = "";if (ret) {tmp_key = st_or_code == KEY_TYPE_OF_ST_STR ? proxy_st_ : oauth_code_;UpdateKeys(st_or_code, tmp_key);Schedule();}prom_ptr->set_value(tmp_key);}).onError([&, prom_ptr](const std::exception& ex){prom_ptr->set_value("");});}...return current_key;
}
二、弱引用
2.1 原理
弱引用(Weak Reference)是一种特殊的引用类型,它不会阻止其所引用的对象被垃圾回收。这在处理回调和长时间运行的任务时非常有用,因为它可以避免因为回调导致的潜在内存泄漏。
2.2 案例一
错误的写法:
class Foo {
public:void start() {std::thread t([this]() {std::this_thread::sleep_for(std::chrono::seconds(1));this->doSomething(); // Undefined behavior if `this` is destroyed!});t.detach();}void doSomething() {std::cout << "Doing something..." << std::endl;}
};
在上述代码中,我们在新线程中访问了this指针。然而,如果新线程开始执行时,this指针所指向的对象已经被销毁,这将导致未定义的行为。
正确的写法:
class Foo : public std::enable_shared_from_this<Foo> {
public:void start() {std::thread t([weak_this = std::weak_ptr<Foo>(shared_from_this())]() {if (auto shared_this = weak_this.lock()) {shared_this->doSomething(); // 安全,只要 `this` 没有被销毁}});t.detach();}void doSomething() {std::cout << "Doing something..." << std::endl;}
};
在修正的代码中,我们使用了弱引用来捕获this指针。这样,即使原始对象被销毁,新线程中也不会访问到无效的this指针。
2.3 案例二:使用base
库的弱引用
base::BindLambda(base::AsWeakPtr(this), [&] { ... })
使用了弱引用。这里,base::AsWeakPtr(this)
将this
指针转换为弱引用,并将其传递给Lambda表达式。这样,在回调执行时,如果this
指针所指向的对象已经被销毁,回调将不会执行,从而避免了潜在的内存泄漏问题。
下面是执行CGI任务时的回调写法。当CGI网络请求回来时,所在的Service类可能已经被析构,所以需要使用base::AsWeakPtr(this)
将this
指针转换为弱引用:
task->SetCallback(base::BindLambda(base::AsWeakPtr(this), [=](network::ProtocolErrorCode pec, const CRTX_WWK::BatchSetLeaderRsp& resp) {LogicErrorCode code = (pec == network::PEC_OK && task->response_head()->errcode() == 0) ? LEC_OK : LEC_ERROR;if(code == LEC_OK) {...}if (!callback.is_null()) callback.Run(code);
}));
ScheduleTask(task.get());
大家可能已经注意到,上面的Lamda回调中,我们不需要再额外判断this
是否已经被析构,因为base库已经替我们提前判断好再回调:
/*** @brief BindLambda 函数实现了便捷的通过 C++ Lambda 表达式来创建 base::Callback 的方法。* 这个重载允许额外传入一个 base::WeakPtr 类型的弱引用,在实际执行 functor 前会检查弱引用的有效性,如果弱引用已经无效,则不会执行 functor。** @param weakptr 额外传递一个弱引用,在 functor 执行前会进行检查,如果该弱引用无效则不会继续调用 functor* @param functor C++ Lambda 表达式* @param params 需要绑定在 Lambda 表达式上的参数** @note 可根据实际情况,选择使用捕获或者绑定的方式传递参数。*/
template <typename SupportWeakPtrType, typename Functor, typename ...Params>
auto BindLambda(const WeakPtr<SupportWeakPtrType>& weakptr, const Functor& functor, const Params&... params) -> decltype(BindLambda(functor, params...)) {return _WrapWeakCallback(BindLambda(functor, params...), weakptr);
}template <typename SupportWeakPtrType, typename RetType, typename ...Params>
base::Callback<RetType(Params...)> _WrapWeakCallback(const base::Callback<RetType(Params...)>& callback, const WeakPtr<SupportWeakPtrType>& weakptr) {return base::Bind(&_RunWeakCallbackInternalRet<SupportWeakPtrType, RetType, Params...>, weakptr, callback);
}template <typename SupportWeakPtrType, typename RetType, typename ...Params>
RetType _RunWeakCallbackInternalRet(const WeakPtr<SupportWeakPtrType>& weakptr, const base::Callback<RetType(Params...)>& callback, Params... params) {if (weakptr.get()) {return callback.Run(params...);}return RetType();
}
-
BindLambda
函数接受一个弱引用(weakptr
)、一个Lambda表达式(functor
)和一些参数(params
)。它将创建一个回调函数,该回调在执行前会检查弱引用的有效性。如果弱引用无效,则不会执行Lambda表达式。 -
_WrapWeakCallback
函数接受一个回调函数(callback
)和一个弱引用(weakptr
)。它将创建一个新的回调函数,该回调函数在调用之前会检查弱引用的有效性。 -
_RunWeakCallbackInternalRet
函数在弱引用有效时执行回调函数(callback
),否则返回默认值。这个函数实际上是在执行回调之前检查弱引用的有效性的地方。
三、总结
在C++回调中,我们需要根据具体情况选择合适的捕获方式(按值捕获、按引用捕获或弱引用)。在处理回调和长时间运行的任务时,为了避免内存泄漏和访问无效变量的问题,我们通常需要使用按值捕获和弱引用。
最后我们总结一下本文:
类型 | 原理 | 注意事项 |
---|---|---|
按值捕获 | 将外部变量的值复制到Lambda表达式的闭包中,使得Lambda表达式在执行时使用的是复制的值,而不是原始变量的值。 | 如果捕获的变量在Lambda表达式执行时已经离开了作用域,那么按值捕获就是安全的,因为Lambda表达式中使用的是变量的副本。 |
按引用捕获 | 将外部变量的引用存储在Lambda表达式的闭包中,使得Lambda表达式在执行时直接访问的是原始变量。 | 如果捕获的变量在Lambda表达式执行时已经离开了作用域,那么按引用捕获就可能导致未定义的行为。因此,使用按引用捕获时,需要确保捕获的变量在Lambda表达式执行时仍然有效。 |
弱引用 | 弱引用是一种特殊的引用类型,它不会阻止其所引用的对象被垃圾回收。这在处理回调和长时间运行的任务时非常有用,因为它可以避免因为回调导致的潜在内存泄漏。 | 如果弱引用所引用的对象在回调执行时已经被销毁,那么回调将不会执行,从而避免了潜在的内存泄漏问题。因此,使用弱引用时,需要确保在回调执行时,弱引用所引用的对象仍然存在。 |
相关文章:
掌握C++回调:按值捕获、按引用捕获与弱引用
文章目录 一、按引用捕获和按值捕获1.1 原理1.2 案例 二、弱引用2.1 原理2.2 案例一2.3 案例二:使用base库的弱引用 三、总结 在C回调中,当使用Lambda表达式捕获外部变量时,有两种捕获方式:按值捕获和按引用捕获。 一、按引用捕获…...

抖音运营_如何做出优质的短视频
目录 一 短视频内容的构成 1 图像 2 字幕 3 声音 4 特效 5 描述 6 评论 二 短视频的热门类型 1 颜值圈粉类 2 知识教学类 3 幽默搞笑类 4 商品展示类 5 才艺技能类 6 评论解说类 三 热门短视频的特征 1 产生共鸣 2 正能量 3 紧跟热点话题 4 富有创意 四 短视…...

Day21:Leetcode513.找树左下角的值 +112. 路径总和 113.路径总和ii + 106.从中序与后序遍历序列构造二叉树
LeetCode:513.找树左下角的值 解决方案: 1.思路 在遍历一个节点时,需要先把它的非空右子节点放入队列,然后再把它的非空左子节点放入队列,这样才能保证从右到左遍历每一层的节点。广度优先搜索所遍历的最后一个节点…...
Java数据结构和算法(B树)
前言 B树又叫平衡的多路搜索树;平衡的意思是又满足平衡二叉树的一些性质,左树大于右树; 多路意思是,可以多个结点,不再是像二叉树只有两个结点; 实现原理 B树是一种自平衡的搜索树,通常用于实…...
成为程序员后我都明白了什么?从入行到弃坑?
作为一个入行近10年的php程序员,真心感觉一切都才刚开始,对计算机,编程语言的理解也好,程序员中年危机也罢,之前都是听别人说的,真的自己到了这个水平,这个年龄才深刻体会到这其中的种种。 我一…...
python --创建固定字符串长度,先进先出
a 123def concatenate_within_limit(b, new_string):# 计算新字符串与a的长度之和a btotal_length len(a) len(new_string)# 如果长度超过1024,从前面删除足够的字符if total_length > 5:diff total_length - 5a a[diff:] new_string # 删除前diff个字符…...
容器化部署
目录 docker容器化部署 怎样使用Docker Compose或Kubernetes等容器编排工具来管理和扩展联邦学习系统 使用Docker Compose...
国产数据库TiDB的常用方法
TiDB的常用方法主要涉及安装配置、数据操作、性能调优以及监控和维护等方面。以下是对这些常用方法的归纳和介绍: 1. 安装与配置 安装TiDB:根据官方文档的指引,用户可以按照步骤进行TiDB的安装。配置TiDB:安装完成后,…...

基于DdddOcr通用验证码离线本地识别SDK搭建个人云打码接口Api
前言 最近介绍了一款免费的验证码识别网站,识别效率太低,考虑到ddddocr是开源的,决定搭建搭建一个,发现原作者sml2h3已经推出好久了,但是网上没有宝塔安装的教程,于是本次通过宝塔搭建属于自己的带带弟弟OCR通用验证码离线本地识别 原项目地址:https://github.com/sml2…...

2、xss-labs之level2
1、打开页面 2、传入xss代码 payload:<script>alert(xss)</script>,发现返回<script>alert(xss)</script> 3、分析原因 打开f12,没什么发现 看后端源码,在这form表单通过get获取keyword的值赋给$str&am…...

人才测评的应用:人才选拔,岗位晋升,面试招聘测评
人才测评自诞生以来,就被广泛应用在各大方面,不仅是我们熟悉的招聘上,还有其他考核和晋升,都会需要用到人才测评。不知道怎么招聘?或者不懂得如何实现人才晋升?都可以参考人才测评,利用它帮我们…...
前端面试题日常练-day33 【面试题】
题目 希望这些选择题能够帮助您进行前端面试的准备,答案在文末。 在jQuery中,以下哪个选项用于在元素上绑定一个点击事件? a) click() b) bind() c) on() d) trigger() jQuery中,以下哪个选项用于获取元素的属性值? …...

非整数倍数据位宽转换24to128
描述 实现数据位宽转换电路,实现24bit数据输入转换为128bit数据输出。其中,先到的数据应置于输出的高bit位。 电路的接口如下图所示。valid_in用来指示数据输入data_in的有效性,valid_out用来指示数据输出data_out的有效性;clk是时…...

html通过数据改变,图片跟着改变
改变前 改变后 通过数据来控制样式展示 <template><div>通过num控制图标是否更改{{num}}<div class"box"><!-- 如果num大于1则是另一种,样式,如果小时1,则是另一种样式 --><div class"item&qu…...

centos7.9 安装SqlServer
1、导入Microsoft SQL Server CentOS存储库: sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2019.repo2、安装SQL Server: sudo yum install -y mssql-server假如机器内存不足2G 需要对…...

Idea中flume的Interceptor的编写教程
1.新建-项目-新建项目 注意位置是将来打包文件存放的位置,即我们打包好的文件在这/export/data个目录下寻找 2. 在maven项目中导入依赖 Pom.xml文件中写入 <dependencies> <dependency> <groupId>org.apache.flume</groupId> <artifa…...
java单元测试:JUnit测试运行器
JUnit测试运行器(Test Runner)决定了JUnit如何执行测试。JUnit有多个测试运行器,每个运行器都有特定的功能和用途。 1. 默认运行器 当没有显式指定运行器时,JUnit会使用默认运行器,这在JUnit 4和JUnit 5之间有所不同…...

网络模型—BIO、NIO、IO多路复用、信号驱动IO、异步IO
一、用户空间和内核空间 以Linux系统为例,ubuntu和CentOS是Linux的两种比较常见的发行版,任何Linux发行版,其系统内核都是Linux。我们在发行版上操作应用,如Redis、Mysql等其实是无法直接执行访问计算机硬件(如cpu,内存…...

智能语义识别电影机器人的rasa实现
文章目录 0.前言1.项目整体框架2.rasa训练数据结构4.rasa启动命令及用到的API 0.前言 最近做了一个智能电影机器人的项目,我主要负责用户语义意图识别,用的框架是rasa,对应的版本为 3.6.15,对应的安装命令为: pip3 install rasa…...
C# 实现腾讯云 IM 常用 REST API 之会话管理
目录 关于腾讯 IM REST API 开发前准备 范例运行环境 常用会话管理API 查询账号会话总未读数 查询单聊会话消息记录 下载最近会话记录 小结 关于腾讯 IM REST API REST API 是腾讯即时通信 IM 提供给服务端的一组 HTTP 后台管理接口,如消息管理、群组管理…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...