std::unique_ptr和lambda表达式混用踩坑日记

一.unique_ptr的 引用捕获 vs 转移所有权

1.问题

我们知道unique_ptr是c++的一种不可拷贝的类型,即以下操作是非法的:

std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 = p1;  // invalid, Call to implicitly-deleted copy constructor of 'unique_ptr<int>'
std::unique_ptr<int> p3(p1);  // invalid, Call to implicitly-deleted copy constructor of 'unique_ptr<int>'

因此无法在lambda闭包中直接按值捕获unique_ptr

std::unique_ptr<int> p1 = std::make_unique<int>(10);
auto closure = [p1] {     // invalid, Call to implicitly-deleted copy constructor of 'unique_ptr<int>'printf("p1 = %d \n", *p1);
};

有2种方式可以在lambda闭包中访问外部的unique_ptr引用捕获转移所有权
引用捕获和转移所有权都可以使得unique_ptr可以在lambda闭包中使用,区别在于:

2.引用捕获

本质上是获得了外部作用域unique_ptr变量的引用,其生命周期完全受外部作用域控制

std::unique_ptr<int> p1 = std::make_unique<int>(10);
auto closure = [&p1] {printf("p1 = %d \n", *p1);
};

缺陷:unique_ptr生命周期不受控,lambda闭包内部访问时可能已经被外部修改或释放。如下面这种情况:

{std::unique_ptr<int> ptr = std::make_unique<int>(10);std::thread([&ptr]() {printf("ohter thread  ptr = %d \n", *ptr); // run time error: Thread 2: EXC_BAD_ACCESS (code=1, address=0x0)}).detach();printf("main thread  ptr = %d \n", *ptr);
}// console output: 
// main thread  ptr = 10 

3.转移所有权

将外部作用域unique_ptr的所有权转移到lambda闭包内部,生命周期完全由lambda闭包内部控制

std::unique_ptr<int> p1 = std::make_unique<int>(10);
auto closure = [p1 = std::move(p1)] {printf("p1 = %d \n", *p1);
};

缺陷:转移之后,外部将无法再使用这个unique_ptr。如下面这种情况:

{std::unique_ptr<int> ptr = std::make_unique<int>(10);std::thread([ptr = std::move(ptr)]() {printf("ohter thread  ptr = %d \n", *ptr);}).detach();printf("main thread  ptr = %d \n", *ptr); // run time error: Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
}

二、在lambda闭包使用转移所有权获取unique_ptr时导致的编译问题

1.问题

看个例子🌰:

std::function<void()> func() {auto ptr = std::make_unique<int>(1);return [ptr = std::move(ptr)]() {printf("*ptr = %d \n", *ptr);};
}

编译错误

include/c++/v1/__memory/compressed_pair.h:46:9: error: call to implicitly-deleted copy constructor of '(lambda at……
copy constructor of ‘’ is implicitly deleted because field ‘’ has a deleted copy constructor
return ptr = std::move(ptr) {

分析错误的原因,首先要先了解c++的lambda表达式相关原理。

lambda表达式事实上是定义了一个匿名类,并立即构建了一个该匿名类的实例对象。这个类内部重写了运算符operator(),以便在使用的时候可以当成函数的方式去调用。

于是,上面例子🌰中的代码可以写成等价于以下代码:

struct __anonymous {__anonymous(std::unique_ptr<int> ptr) : _ptr(std::move(ptr)) {}void operator()() {printf("*ptr = %d \n", *_ptr);}
private:std::unique_ptr<int> _ptr;
};std::function<void()> func() {auto ptr = std::make_unique<int>(1);return __anonymous(std::move(ptr));
}

当然,这段代码也有近乎类似的编译错误

include/c++/v1/__memory/compressed_pair.h:46:9: error: call to implicitly-deleted copy constructor of ‘__anonymous’
copy constructor of ‘__anonymous’ is implicitly deleted because field ‘_ptr’ has a deleted copy constructor std::unique_ptr _ptr;

2.原因分析:

当类中包含有不可拷贝的成员变量的时候(如unique_ptr),这个类就无法自动生成默认的拷贝函数(因为这个类里面包含有不可拷贝的成员变量)。因此当需要对这个类的对象进行拷贝操作的时候,如:

__anonymous a1;  
__anonymous a2 = a1; 
__anonymous a3(a1)

就无法正确定义这个类,因此产生编译错误。

⚠️⚠️⚠️如果实际使用的时候并没有上述拷贝操作,那么编译也不会有问题。主要是有触发拷贝类对象的时候有问题。

这里这个例子🌰为什么会调用类的拷贝?
我们看到func()方法返回的是一个std::function<void()>对象,在构造std::function对象的时候,这个lambda表达式所构造的匿名的类对象,就会触发拷贝操作。而使用lambda闭包来构造std::function的时候,拷贝操作是无法避免的。

3.解决方案:

1.直接返回lambda表达式,而非构造std::function

auto func() {auto ptr = std::make_unique<int>(1);return [ptr = std::move(ptr)]() {printf("*ptr = %d \n", *ptr);};
}

⚠️方法1并没有从根本上解决问题,只是避免了去构造std::function,如果有强制使用std::function的情况,错误仍然会出现

2.使用std::reflambda表达式包装成std::function(只是解决了编译问题,运行仍然报错)

std::function<void()> func() {auto ptr = std::make_unique<int>(1);auto lambda = [ptr = std::move(ptr)]() {printf("*ptr = %d \n", *ptr);};return std::ref(lambda);
}

❌这里运行会报错,因为return之后,auto lambda这个局部变量就会被释放掉,相对应的这个匿名类的内部被std::move进来的这个std::unique_ptr也会被释放掉,因此外部在调用这个std::function的时候, std::unique_ptr已经失效了

3.待补充

参考资料:

https://taylorconor.com/blog/noncopyable-lambdas/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/72236.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深度学习实验】数据可视化

目录 一、实验介绍 二、实验环境 三、实验内容 0. 导入库 1. 归一化处理 归一化 实验内容 2. 绘制归一化数据折线图 报错 解决 3. 计算移动平均值SMA 移动平均值 实验内容 4. 绘制移动平均值折线图 5 .同时绘制两图 6. array转换为tensor张量 7. 打印张量 一、…

数据结构与算法:练习与实践的重要性

文章目录 为什么练习与实践很重要&#xff1f;1. 熟练应用2. 问题解决能力3. 代码效率4. 面试准备 如何练习与实践&#xff1f;1. 在线评测平台2. 自主设计数据结构3. 解决不同类型的问题 持续学习与实践 &#x1f389;欢迎来到数据结构学习专栏~数据结构与算法&#xff1a;练习…

golang遍历map的方法

在Go语言中&#xff0c;可以使用range关键字来遍历一个map。range关键字会返回两个值&#xff1a;key和value。 以下是遍历map的示例代码&#xff1a; package main import "fmt" func main() { myMap : map[string]int{ "apple": 1, "banana…

Edge被2345浏览器劫持 解决方法

Edge 被 hao123 劫持解决方法_edge被hao123锁定改不了_小子宝丁的博客-CSDN博客

482. 合唱队形

Powered by:NEFU AB-IN Link 文章目录 482. 合唱队形题意思路代码 482. 合唱队形 题意 略 &#xff08;形成山丘式的队形&#xff0c;最少提几个人&#xff09; 思路 前后各做一次LIS&#xff08;必须是dp&#xff09; f[i] 就表示以i为结尾的正序的LIS g[i] 就表示以i为结尾的…

【linux命令讲解大全】033.Linux常用命令之atrm、colrm和hdparm

文章目录 atrm补充说明语法选项参数 实例 colrm补充说明语法参数 hdparm补充说明语法选项参数 实例 从零学 python atrm 删除待执行任务队列中的指定任务 补充说明 atrm命令用于删除待执行任务队列中的指定任务。 语法 atrm [选项] [参数]选项 -V&#xff1a;显示版本号。…

【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; list模拟实现 1. 前言2. list类的大致框架与结构…

Linux下的系统编程——共享存储映射(十)

前言&#xff1a; mmap是一种内存映射文件的方法&#xff0c;即将一个文件或者其它对象映射到进程的地址空间&#xff0c;实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后&#xff0c;进程就可以采用指针的方式读写操作这一段内存&…

车船边缘网关是如何给车辆船只定位的?

随着智能交通系统的不断发展&#xff0c;车路协同成为了重要的研究方向之一。而AI边缘计算网关在这个领域中发挥着至关重要的作用。本文将重点介绍AI边缘计算网关在车路协同中的应用&#xff0c;并强调其中的重点词汇或短语。 首先&#xff0c;什么是AI边缘计算网关&#xff1…

【2023年数学建模国赛】D题解题思路

2023年数学建模国赛D题解题思路 为了解决问题1、问题2和问题3&#xff0c;我们可以采用动态规划方法来制定生产计划&#xff0c;考虑了不确定性因素和多种可能情况的预案集。首先&#xff0c;我们需要定义一些变量和符号&#xff1a; T T T&#xff1a;总的养殖周期&#xff0…

使用命令行创建仓库

如果你还没有任何代码&#xff0c;可以通过命令行工具创建一个全新的Git仓库并初始化到本项目仓库中。 git clone https://e.coding.net/***/neurosens.git cd neurosens echo "# neurosens" >> README.md git add README.md git commit -m "first commi…

2023年09月编程语言流行度排名

点击查看最新编程语言流行度排名&#xff08;每月更新&#xff09; 2023年09月编程语言流行度排名 编程语言流行度排名是通过分析在谷歌上搜索语言教程的频率而创建的 一门语言教程被搜索的次数越多&#xff0c;大家就会认为该语言越受欢迎。这是一个领先指标。原始数据来自…

解决windows下git操作提示用户名密码错误的问题

当代码从一个平台切换到另一个平台的时候&#xff0c;需要做两步操作&#xff0c;第一步就是更新git的仓库地址&#xff0c;在项目的.git/config文件里面修改&#xff0c;这一步做完之后&#xff0c;就可以推送代码到新的仓库了&#xff0c;这里就是重点来了。 一般第一次推动代…

27.方向标

题目 描述 一位木匠收到了一个木制指示牌的订单。每块木板必须与前一块垂直对齐&#xff0c;要么与前一个箭头的基部对齐&#xff0c;要么与相反的一侧对齐&#xff0c;在那里用特制的螺钉固定。两块木板必须重叠。木匠将设计师发送的草图编码成了一个整数序列&#xff0c;但…

lv3 嵌入式开发-7 linux shell脚本编程(分支语句、循环语句)

目录 1 分支语句 2 多路分支语句 3 for的用法 4 while的用法 5 循环控制语句 6 练习 1 分支语句 语法结构: if 表达式then 命令表fi 如果表达式为真, 则执行命令表中的命令; 否则退出if语句, 即执行fi后面的语句。 if和fi是条件语句的语句括号, 必须成对使用; …

C++,day0907

#include <iostream>using namespace std; struct stu { private:int num; private:double score[32];public:void setNum(){cout <<"请输入学生人数:";cin >>num;}void input(){cout<<"请输入学生的成绩:"<<endl;for(int i…

ARM DIY(九)陀螺仪调试

前言 今天调试六轴陀螺仪 MPU6050 硬件 硬件很简单&#xff0c;使用 I2C 接口&#xff0c;并且没有使用中断引脚。 焊接上 MPU6050 芯片和上拉电阻、滤波电容。 检测 MPU6050 是挂在 I2C-0 上的&#xff0c;I2C-0 控制器的驱动已 OK&#xff0c;所以直接使用 I2C-0 检测 …

Linux与shell命令行学习

文章目录 走进shell基本的bash shell命令2.1 遍历目录 cd2.2 查看文件和目录列表 ls2.3 创建文件 touch2.4 复制文件 cp2.5 自动补全 tab2.6 链接文件 ln2.7 文件重命名 mv2.8 删除文件 rm2.9 创建目录 mkdir2.10 删除目录 rmdir2.11 查看文件类型 file2.12 查看整个文件 cat、…

ElementUI浅尝辄止19:Badge 标记

出现在按钮、图标旁的数字或状态标记。 1.如何使用&#xff1f; 可展示新消息数量。 //定义value属性&#xff0c;它接受Number或者String。<el-badge :value"12" class"item"><el-button size"small">评论</el-button> <…

ABAP BAPI_ACC_DOCUMENT_POST 中 EXTENSION1的用法

BAPI_ACC_DOCUMENT_POST 在过账会计凭证时候&#xff0c;经常会发现一些标准字段在参数中并没有 可以通过CMOD/SMOD增强出口--》ACBAPI01--》EXIT_SAPLACC4_001--》ZXACCU15 示例代码&#xff1a; DATA: wa_extension TYPE bapiextc,it_extension TYPE STANDARD TABLE OF ba…