当前位置: 首页 > news >正文

掌握C++回调:按值捕获、按引用捕获与弱引用

在C++回调中,当使用Lambda表达式捕获外部变量时,有两种捕获方式:按值捕获和按引用捕获。

一、按值捕获

1.1 原理

按值捕获是将外部变量的值复制到Lambda表达式的闭包中。这样,当Lambda表达式执行时,它将使用这个复制的值,而不是原始变量的值。这种方式可以避免在回调执行时,原始变量已经失效的问题。

在给定的代码中,ph.then([&, st_or_code, prom_ptr](bool ret) { ... }) 使用了按值捕获。这意味着st_or_codeprom_ptr的值将在Lambda表达式创建时被复制,并在回调执行时使用这些复制的值。这样,即使原始的st_or_code变量在栈上失效,回调中仍然可以安全地使用其复制的值。

1.2 案例

错误的写法:

void foo() {int x = 10;std::thread t([&]() {std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << x << std::endl;  // Undefined behavior!});t.detach();
}

在上述代码中,我们创建了一个线程,并在该线程中访问了局部变量x。然而,因为我们使用了按引用捕获,当新线程开始执行时,局部变量x可能已经离开了作用域并被销毁,这将导致未定义的行为。

正确的写法:

void foo() {int x = 10;std::thread t([x]() {  // 按值捕获std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << x << std::endl;  // 安全});t.detach();
}

在修正的代码中,我们使用按值捕获,这样即使原始变量x离开了作用域,线程中仍然可以安全地访问它的副本。

二、按引用捕获

2.1 原理

按引用捕获是将外部变量的引用存储在Lambda表达式的闭包中。这样,当Lambda表达式执行时,它将直接访问原始变量。这种方式在某些情况下可能导致问题,例如,当回调执行时,原始变量已经失效(例如,原始变量是栈上的局部变量,而回调在该变量离开作用域后执行)。

在给定的代码中,[&] 表示按引用捕获所有外部变量。这在某些情况下可能不安全,因此需要注意确保捕获的变量在回调执行时仍然有效。

2.2 案例

错误的写法:

void foo(std::vector<int>& v) {std::thread t([&]() {std::this_thread::sleep_for(std::chrono::seconds(1));v.push_back(10);  // Undefined behavior if `v` is destroyed!});t.detach();
}

在上述代码中,我们创建了一个线程,并在该线程中修改了向量v。然而,因为我们使用了按引用捕获,当新线程开始执行时,向量v可能已经被销毁,这将导致未定义的行为。

正确的写法:

void foo(std::vector<int>& v) {std::thread t([&v]() {  // 按引用捕获std::this_thread::sleep_for(std::chrono::seconds(1));v.push_back(10);  // 安全,只要确保 `v` 在此期间不会被销毁});t.join();  // 等待线程完成,确保 `v` 不会被提前销毁
}

在修正的代码中,我们在新线程执行期间,通过调用join()方法阻塞主线程,确保向量v不会被提前销毁。

三、弱引用

3.1 原理

弱引用(Weak Reference)是一种特殊的引用类型,它不会阻止其所引用的对象被垃圾回收。这在处理回调和长时间运行的任务时非常有用,因为它可以避免因为回调导致的潜在内存泄漏。

在给定的代码中,base::BindLambda(base::AsWeakPtr(this), [&] { ... }) 使用了弱引用。这里,base::AsWeakPtr(this)this指针转换为弱引用,并将其传递给Lambda表达式。这样,在回调执行时,如果this指针所指向的对象已经被销毁,回调将不会执行,从而避免了潜在的内存泄漏问题。

3.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指针。

四、总结

在C++回调中,我们需要根据具体情况选择合适的捕获方式(按值捕获、按引用捕获或弱引用)。在处理回调和长时间运行的任务时,为了避免内存泄漏和访问无效变量的问题,我们通常需要使用按值捕获和弱引用。

最后我们总结一下本文:

类型原理注意事项
按值捕获将外部变量的值复制到Lambda表达式的闭包中,使得Lambda表达式在执行时使用的是复制的值,而不是原始变量的值。如果捕获的变量在Lambda表达式执行时已经离开了作用域,那么按值捕获就是安全的,因为Lambda表达式中使用的是变量的副本。
按引用捕获将外部变量的引用存储在Lambda表达式的闭包中,使得Lambda表达式在执行时直接访问的是原始变量。如果捕获的变量在Lambda表达式执行时已经离开了作用域,那么按引用捕获就可能导致未定义的行为。因此,使用按引用捕获时,需要确保捕获的变量在Lambda表达式执行时仍然有效。
弱引用弱引用是一种特殊的引用类型,它不会阻止其所引用的对象被垃圾回收。这在处理回调和长时间运行的任务时非常有用,因为它可以避免因为回调导致的潜在内存泄漏。如果弱引用所引用的对象在回调执行时已经被销毁,那么回调将不会执行,从而避免了潜在的内存泄漏问题。因此,使用弱引用时,需要确保在回调执行时,弱引用所引用的对象仍然存在。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • NB65 第k轻的牛牛
  • windows下nginx配置https证书
  • 无人机监测系统:天空之眼,精准掌握地球脉动
  • osgearth 3.5 vs 2019编译
  • 【LeetCode 随笔】面试经典 150 题【中等+困难】持续更新中。。。
  • 使用cockpit管理服务器
  • 在 Visual Studio 2022 (VS2022) 中删除 Git 分支的步骤如下
  • 第十三期Big Demo Day聚焦Web3前沿,FaceN.AI项目路演揭幕创新技术
  • ClickHouse 24.4 版本发布说明
  • 基于hive的酒店价格数据可视化分析系统设计和实现
  • AcW木棒-XMUOJ恢复破碎的符咒木牌-DFS与剪枝
  • JVM(5):虚拟机性能分析和故障解决工具概述
  • WebSocket——相关介绍以及后端配置
  • 作文笔记9 描写方法
  • unity开发Hololens,使用unity自带的UGUI
  • [case10]使用RSQL实现端到端的动态查询
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • echarts的各种常用效果展示
  • egg(89)--egg之redis的发布和订阅
  • opencv python Meanshift 和 Camshift
  • Python进阶细节
  • windows下如何用phpstorm同步测试服务器
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 前嗅ForeSpider采集配置界面介绍
  • 小程序01:wepy框架整合iview webapp UI
  • 小程序开发之路(一)
  • puppet连载22:define用法
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • ​插件化DPI在商用WIFI中的价值
  • ​马来语翻译中文去哪比较好?
  • # 职场生活之道:善于团结
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #考研#计算机文化知识1(局域网及网络互联)
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (4)事件处理——(7)简单事件(Simple events)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第6节 (嵌套的Finally代码块)
  • (Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)
  • (汇总)os模块以及shutil模块对文件的操作
  • (一一四)第九章编程练习
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .net2005怎么读string形的xml,不是xml文件。
  • .NET文档生成工具ADB使用图文教程
  • /etc/skel 目录作用
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [240903] Qwen2-VL: 更清晰地看世界 | Elasticsearch 再次拥抱开源!
  • [3300万人的聊天室] 作为产品的上游公司该如何?
  • [BUG] Authentication Error
  • [BZOJ] 3262: 陌上花开
  • [C++]命名空间等——喵喵要吃C嘎嘎
  • [Firefly-Linux] RK3568修改控制台DEBUG为普通串口UART