C++ lambda 内 std::move 失效问题的思考
最近在学习 C++ Move 时,有看到这样一个代码需求:在 lambda 中,将一个捕获参数 move 给另外一个变量。
看似一个很简单常规的操作,然而这个 move 动作却没有生效。
具体代码如下
std::vector<int> vec = {1,2,3};
auto func = [=](){
auto vec2 = std::move(vec);
std::cout << vec.size() << std::endl; // 输出:3
std::cout << vec2.size() << std::endl; // 输出:3
};
代码可在 wandbox 运行。
我们期望的是,将对变量 vec 调用 std::move 后,数据将会移动至变量 vec2, 此时 vec 里面应该没有数据了。但是通过打印 vec.size() 发现 vec 中的数据并没有按预期移走。
这也就意味着,构造 vec2 时并没有按预期调用移动构造函数,而是调用了拷贝构造函数。
为什么会造成这个问题呢, 我们需要结合 std::move 和 lambda 的原理看下。(最终的解决方案可以直接看 这里)
std::move 的本质
\(std::move()\) 位于
#include <utilty>中,但一般无需特地引入,iostream,string等头文件会包含
对于 \(std::move\),有两点需要注意:
- \(std::move\) 中到底做了什么事情
- \(std::move\) 是否可以保证数据一定能移动成功
对于第二点来说,答案显然是不能。这也是本文的问题所在。那么 std::move 实际上是做了什么事情呢?
对于 std::move,其实现大致如下:
template<typename T>
decltype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
从代码可以看出,std::move 本质上是调用了 static_cast 做了一层强制转换,强制转换的目标类型是 remove_reference_t<T>&&,remove_reference_t 是为了去除类型本身的引用,例如左值引用。总结来说,std::move 本质上是将对象强制转换为了右值引用。
那么,为什么我们通常使用 std::move 实现移动语义,可以将一个对象的数据移给另外一个对象?
这是因为 std::move 配合了移动构造函数使用,本质上是移动构造函数起了作用。移动构造函数的一般定义如下:
class A
{
public:
A (A &&);
}
可以看到移动构造函数的参数就是个右值引用 A&&,因此 A a = std::move(b);, 本质上是先将 b 强制转化了右值引用 A&&,
然后触发了移动构造函数,在移动构造函数中,完成了对象 b 的数据到对象 a 的移动。
那么,在哪些情况下,A a = std::move(b); 会失效呢?
显然是,当 std::move 强转后的类型不是 A&&,这样就不会命中移动构造函数。
例如:
const std::string str = "123";
std::string str2(std::move(str));
这个时候,对 str 对象调用 std::move,强转出来的类型将会是 const string&&, 这样移动构造函数就不会起作用了,但是这个类型却可以令复制构造函数生效。
结合本文最初的问题,在 lambda 中 move 没有生效,显然也是 std::move 强转的类型不是 std::vector<int>&&, 才导致了没有 move 成功。
那么,为什么会出现这个问题呢,我们需要理解下 lambda 的工作原理。
lambda 闭包原理
对于 c++ 的 lambda,编译器会将 lambda 转化为一个独一无二的闭包类。而 lambda 对象最终会转化成这个闭包类的对象。
对于本文最初的这个 lambda 来说,最终实际上转化成了这么一个类型
// 转换前
auto func = [=](){
auto vec2 = std::move(vec);
};
// 转换后
class ClosureFunc{
public:
void operator() const{
auto vec2 = std::move(vec);
};
private:
std::vector<int> vec;
};
ClosureFunc func;
这里需要注意, lambda 的默认行为是,生成的闭包类的 operator() 默认被 const 修饰。
那么这里问题就来了,当调用 operator() 时, 该闭包类所有的成员变量也是被 const 修饰的,此时对成员变量调用 std::move 将会引发上文中提到的,强转出来的类型将会是 const string&& 问题。因此,移动构造函数将不会被匹配到。
我们最初的问题 lambda 中 std::move 失效的问题,也是因为这个原因。这也很符合 const 函数的语义: const 函数是不能修改成员变量的值。
解决方案
那么,这个应该怎么解决呢?答案是 mutable。即在 lambda 尾部声明一个 mutable,如下:
auto func = [=]() mutable{
auto vec2 = std::move(vec);
};
这样编译器生成的闭包类的 operator() 将会不带 const 了。我们的 std::move 也可以正常转换,实现移动语义了。
std::vector<int> vec = {1,2,3};
auto func = [=]() mutable{
auto vec2 = std::move(vec);
std::cout <<vec.size() << std::endl; // 输出:0
std::cout <<vec2.size() << std::endl; // 输出:3
};
代码可以在 wandbox 运行。
参考
- Lambda 表达式 - cppreference
- Effective Modern c++
- 关于 C++ 右值及 std::move() 的疑问?
C++ lambda 内 std::move 失效问题的思考的更多相关文章
- 第19课 lambda vs std::bind
一. std::bind (一)std::bind实现的关键技术 [编程实验]探索bind原理,实现自己的bind函数 #include <iostream> #include <t ...
- 左值 lvalue,右值 rvalue 和 移动语义 std::move
参考文章: [1] 基础篇:lvalue,rvalue和move [2] 深入浅出 C++ 右值引用 [3] Modern CPP Tutorial [4] 右值引用与转移语义 刷 Leetcode ...
- lambda+mutable配合move实现单函数多程序域
主代码 //-----------------------------------说明一的代码 void fun0{ int t = 10; auto loopFun = [=]() mutable{ ...
- 关于C++11中的std::move和std::forward
std::move是一个用于提示优化的函数,过去的c++98中,由于无法将作为右值的临时变量从左值当中区别出来,所以程序运行时有大量临时变量白白的创建后又立刻销毁,其中又尤其是返回字符串std::st ...
- 右值引用和std::move函数(c++11)
1.对象移动 1)C++11新标准中的一个最主要的特性就是移动而非拷贝对象的能力 2)优势: 在某些情况下,从旧内存拷贝到新内存是不必要的,此时对对象进行移动而非拷贝可以提升性能 有些类如IO类或un ...
- [转载]如何在C++03中模拟C++11的右值引用std::move特性
本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm 引言 众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalu ...
- 透彻理解C++11新特性:右值引用、std::move、std::forward
目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...
- C++ 11中的右值引用以及std::move
看了很多篇文章,现在终于搞懂了C++ 中的右值以及std::move 左值和右值最重要的区别就是右值其实是一个临时的变量 在C++ 11中,也为右值引用增加了新语法,即&& 比 ...
- std::move()和std::forward()
std::move(t)负责将t的类型转换为右值引用,这种功能很有用,可以用在swap中,也可以用来解决完美转发. std::move()的源码如下 template<class _Ty> ...
- C++ 11 右值引用以及std::move
转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...
随机推荐
- Android Gson 混淆问题
开发过程中遇到一个奇怪的问题. 有一个接口,debug 版本接收到云侧下发的字符串后可以通过 gson 将其转换为相应 bean 类,而 release 版本拿到的 bean 总是缺少一个关键的字段, ...
- OpenAI宫斗反转反转再反转,到底是资本任性还是人性扭曲?
最近OpenAI发生了一件大事,创始人山姆·奥特曼被董事会开除了,这在AI界引起了轩然大波. 事件经过 我们先来捋一下事件经过,时间以美国旧金山当地时间为准. 11月17日 11月17日12点(北京时 ...
- Excel做数据分析?是真的很强!
当涉及到数据分析时,Excel无疑是一个功能强大且广泛应用的工具.它提供了丰富的功能和灵活性,使得用户可以进行各种复杂的数据处理和分析.在本文中, 我将详细介绍Excel在数据分析领域的强大功能,包括 ...
- 【已解决】【Tensorflow2.12.0版本以后合并CPU和GPU版】Tensorflow-gpu==2.12.0 安装失败解决办法
直接上解决方式,需要知道原因的看后文. 直接安装 tensroflow,从 2022 年 12 月起 tensorflow-gpu 已经合并到 tensorflow 包中了 pip install t ...
- [ARC161F] Everywhere is Sparser than Whole (Judge)
Problem Statement We define the density of a non-empty simple undirected graph as $\displaystyle\fra ...
- 【scikit-learn基础】--『预处理』之 标准化
数据的预处理是数据分析,或者机器学习训练前的重要步骤.通过数据预处理,可以 提高数据质量,处理数据的缺失值.异常值和重复值等问题,增加数据的准确性和可靠性 整合不同数据,数据的来源和结构可能多种多样, ...
- 在 JMeter 中使用 JSON 提取器提取特定条件下的值
当你需要在 JMeter 中对接收到的 JSON 响应进行处理时,JSON 提取器是一个非常有用的工具.在本文中,我们将讨论如何使用 JSON 提取器来提取特定条件下的值,以满足你的需求. 问题描述 ...
- 【C++】【图像处理】均值滤波 and 高斯滤波 and 中值滤波 (低通滤波器)and Sobel算子边缘提取算法解析(以.raw格式的图像为基础进行图像处理、gray levels:256)
1 void meanFilter(BYTE* image, int width, int height, BYTE* outImg) 2 { 3 //均值滤波 4 int smth[9]; 5 in ...
- 云MSP服务案例丨某知名制造集团的Oracle RAC部署实践
简介: 随着DT时代到来,移动互联网.物联网开始高速发展,更多的企业意识到了数据库架构选型对业务发展的重要性,如何管理和利用好数据已经变得越来越重要.Oracle作为世界上目前最流行的关系型数据库管理 ...
- Weblogic获取端口IP
Weblogic获取端口IP 获取端口IP只为了判断哪个节点 调用 private WebMBeanServer server=new WebMBeanServer(); synlog.info(&q ...