我们提供安全,免费的手游软件下载!

安卓手机游戏下载_安卓手机软件下载_安卓手机应用免费下载-先锋下载

当前位置: 主页 > 软件教程 > 软件教程

解决C++中临时对象被Lambda引用捕获导致的问题

来源:网络 更新时间:2024-09-01 09:31:22

今天我在复习项目过程中积累的技术案例时,回顾了一个小的coredump案例。当时,即使是小组里的一些资深同事也没有发现问题所在。然后我在周末花了两三个小时查找并解决了这个问题。今天,我要做一次系统总结,给出一个能够复现该问题的案例代码。这个案例代码相对简单,便于学习和理解。

1. 简介

原则:临时对象不应该被lambda引用捕获,因为临时对象在所在语句结束时会被析构掉,只能采用值捕获。
当临时对象比较隐蔽时,我们就可能犯这个低级错误。本文将介绍一种情况:以基类智能指针对象的const引用作为函数形参,并在函数内对该参数进行引用捕获,然后进行跨线程异步使用。当函数调用者使用派生类智能指针作为实参时,派生类智能指针对象会向上转换为基类智能指针对象。这个转换是隐式的,产生的对象是临时对象,然后被lambda引用捕获。后续跨线程使用引发了"野引用" coredump问题。

2. 案例

下面给出一个简单的演示代码来模拟这个案例。案例涉及的代码流程,如下图所示:

其中,基类为BaseTask,派生类为DerivedTask,main函数将lambda闭包抛到工作线程中异步执行。
详细示例代码如下:

/**
 * @brief 关键字:lambda、多线程、std::shared_ptr 隐式向上转换
 * g++ main.cc -std=c++17 -O3 -lpthread
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// 定义简易线程池...

上述的例子代码,会出现coredump,或者是没有执行派生类的DoSomething。不符合预期的原因在于,这份代码往一个线程里post lambda函数,lambda函数引用捕获智能指针对象,这是一个临时对象,其离开使用域之后会被析构掉,导致lambda函数在异步线程执行时访问到一个"野引用"出错。而之所以捕获的智能指针是临时对象,是因为调用User.DoJobAsync时发生了类型的向上转换。

上述的例子还比较容易看出来问题点,但当我们的项目代码层次较深时,这类错误就非常难发现。因此之前团队里的资深同事也都无法察觉问题所在。

这类问题有多种解决办法:
(1)方法1:避免出现隐式转换,消除临时对象;
(2)方法2:函数和lambda捕获都修改为裸指针,消除临时对象;引用本质上是指针,需要关注生命周期。既然采用引用参数就表示调用者需要保障对象的生命周期,智能指针的引用在用法上跟指针无异,那么这里不如用裸指针,让调用者更清楚自己需要保障对象的生命周期;
(3)方法3:异步执行时采用值捕获/值传递,不采用引用捕获。值捕获可能导致性能浪费,但具体到本文的例子,这里的性能开销是一个智能指针对象的构造,性能损耗不大,是可接受的。

3. 其他

临时对象的生命周期可以参考这篇文档: https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary