掌握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 后台管理接口,如消息管理、群组管理…...

MySQL之Schema与数据类型优化(三)
Schema与数据类型优化 BLOB和TEXT类型 BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。 实际上它们分别属于两组不同的数据类型家族:字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONG…...

大语言模型发展历史
大语言模型的发展历史可以追溯到自然语言处理(NLP)和机器学习早期的探索,但真正快速发展起来是在深度学习技术兴起之后。以下是大语言模型发展的一个简要历史概述: 早期阶段(20世纪50-90年代): …...

Nginx - 安全基线配置与操作指南
文章目录 概述中间件安全基线配置手册1. 概述1.1 目的1.2 适用范围 2. Nginx基线配置2.1 版本说明2.2 安装目录2.3 用户创建2.4 二进制文件权限2.5 关闭服务器标记2.6 设置 timeout2.7 设置 NGINX 缓冲区2.8 日志配置2.9 日志切割2.10 限制访问 IP2.11 限制仅允许域名访问2.12 …...

简述js的事件循环以及宏任务和微任务
前言 在JavaScript中,任务被分为同步任务和异步任务。 同步任务:这些任务在主线程上顺序执行,不会进入任务队列,而是直接在主线程上排队等待执行。每个同步任务都会阻塞后续任务的执行,直到它自身完成。常见的同步任…...

[力扣题解] 797. 所有可能的路径
题目:797. 所有可能的路径 思路 深度搜索 代码 // 图论哦!class Solution { private:vector<vector<int>> result;vector<int> path;// x : 当前节点void function(vector<vector<int>>& graph, int x){int i;// cout <&l…...

【QT八股文】系列之篇章3 | QT的多线程以及QThread与QObject
【QT八股文】系列之篇章3 | QT的多线程 前言4. 多线程为什么需要使用线程池线程池的基础知识python中创建线程池的方法使用threading库队列Queue来实现线程池使用threadpool模块,这是个python的第三方模块,支持python2和python3 QThread的定义QT多线程知…...

基于python flask的web服务
基本例子 from flask import Flask app Flask(__name__) app.route(/)#检查访问的网址,根路径走这里 def hello_world():return hello world#返回hello worldif __name__ __main__:# 绑定到指定的IP地址和端口app.run(host0.0.0.0, port1000, debugTrue)##绑定端…...

HTTP 响应分割漏洞
HTTP 响应分割漏洞 1.漏洞概述2.漏洞案例 1.漏洞概述 HTTP 响应拆分发生在以下情况: 数据通过不受信任的来源(最常见的是 HTTP 请求)进入 Web 应用程序。该数据包含在发送给 Web 用户的 HTTP 响应标头中,且未经过恶意字符验证。…...

Algoriddim djay Pro Ai for Mac:AI引领,混音新篇章
当AI遇上音乐,会碰撞出怎样的火花?Algoriddim djay Pro Ai for Mac给出了答案。这款专业的DJ混音软件,以AI为引擎,引领我们进入混音的新篇章。 djay Pro Ai for Mac的智能混音功能,让每一位DJ都能感受到前所未有的创作…...

常见算法(3)
1.Arrays 它是一个工具类,主要掌握的其中一个方法是srot(数组,排序规则)。 o1-o2是升序排列,o2-o1是降序排列。 package test02; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparat…...