最近在学习 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::movelambda 的原理看下。(最终的解决方案可以直接看 这里

std::move 的本质

\(std::move()\) 位于 #include <utilty> 中,但一般无需特地引入,iostreamstring 等头文件会包含

对于 \(std::move\),有两点需要注意:

  1. \(std::move\) 中到底做了什么事情
  2. \(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 运行。

参考

C++ lambda 内 std::move 失效问题的思考的更多相关文章

  1. 第19课 lambda vs std::bind

    一. std::bind (一)std::bind实现的关键技术 [编程实验]探索bind原理,实现自己的bind函数 #include <iostream> #include <t ...

  2. 左值 lvalue,右值 rvalue 和 移动语义 std::move

    参考文章: [1] 基础篇:lvalue,rvalue和move [2] 深入浅出 C++ 右值引用 [3] Modern CPP Tutorial [4] 右值引用与转移语义 刷 Leetcode ...

  3. lambda+mutable配合move实现单函数多程序域

    主代码 //-----------------------------------说明一的代码 void fun0{ int t = 10; auto loopFun = [=]() mutable{ ...

  4. 关于C++11中的std::move和std::forward

    std::move是一个用于提示优化的函数,过去的c++98中,由于无法将作为右值的临时变量从左值当中区别出来,所以程序运行时有大量临时变量白白的创建后又立刻销毁,其中又尤其是返回字符串std::st ...

  5. 右值引用和std::move函数(c++11)

    1.对象移动 1)C++11新标准中的一个最主要的特性就是移动而非拷贝对象的能力 2)优势: 在某些情况下,从旧内存拷贝到新内存是不必要的,此时对对象进行移动而非拷贝可以提升性能 有些类如IO类或un ...

  6. [转载]如何在C++03中模拟C++11的右值引用std::move特性

    本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm 引言 众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalu ...

  7. 透彻理解C++11新特性:右值引用、std::move、std::forward

    目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...

  8. C++ 11中的右值引用以及std::move

    看了很多篇文章,现在终于搞懂了C++ 中的右值以及std::move   左值和右值最重要的区别就是右值其实是一个临时的变量 在C++ 11中,也为右值引用增加了新语法,即&&   比 ...

  9. std::move()和std::forward()

    std::move(t)负责将t的类型转换为右值引用,这种功能很有用,可以用在swap中,也可以用来解决完美转发. std::move()的源码如下 template<class _Ty> ...

  10. C++ 11 右值引用以及std::move

    转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...

随机推荐

  1. Android Gson 混淆问题

    开发过程中遇到一个奇怪的问题. 有一个接口,debug 版本接收到云侧下发的字符串后可以通过 gson 将其转换为相应 bean 类,而 release 版本拿到的 bean 总是缺少一个关键的字段, ...

  2. OpenAI宫斗反转反转再反转,到底是资本任性还是人性扭曲?

    最近OpenAI发生了一件大事,创始人山姆·奥特曼被董事会开除了,这在AI界引起了轩然大波. 事件经过 我们先来捋一下事件经过,时间以美国旧金山当地时间为准. 11月17日 11月17日12点(北京时 ...

  3. Excel做数据分析?是真的很强!

    当涉及到数据分析时,Excel无疑是一个功能强大且广泛应用的工具.它提供了丰富的功能和灵活性,使得用户可以进行各种复杂的数据处理和分析.在本文中, 我将详细介绍Excel在数据分析领域的强大功能,包括 ...

  4. 【已解决】【Tensorflow2.12.0版本以后合并CPU和GPU版】Tensorflow-gpu==2.12.0 安装失败解决办法

    直接上解决方式,需要知道原因的看后文. 直接安装 tensroflow,从 2022 年 12 月起 tensorflow-gpu 已经合并到 tensorflow 包中了 pip install t ...

  5. [ARC161F] Everywhere is Sparser than Whole (Judge)

    Problem Statement We define the density of a non-empty simple undirected graph as $\displaystyle\fra ...

  6. 【scikit-learn基础】--『预处理』之 标准化

    数据的预处理是数据分析,或者机器学习训练前的重要步骤.通过数据预处理,可以 提高数据质量,处理数据的缺失值.异常值和重复值等问题,增加数据的准确性和可靠性 整合不同数据,数据的来源和结构可能多种多样, ...

  7. 在 JMeter 中使用 JSON 提取器提取特定条件下的值

    当你需要在 JMeter 中对接收到的 JSON 响应进行处理时,JSON 提取器是一个非常有用的工具.在本文中,我们将讨论如何使用 JSON 提取器来提取特定条件下的值,以满足你的需求. 问题描述 ...

  8. 【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 ...

  9. 云MSP服务案例丨某知名制造集团的Oracle RAC部署实践

    简介: 随着DT时代到来,移动互联网.物联网开始高速发展,更多的企业意识到了数据库架构选型对业务发展的重要性,如何管理和利用好数据已经变得越来越重要.Oracle作为世界上目前最流行的关系型数据库管理 ...

  10. Weblogic获取端口IP

    Weblogic获取端口IP 获取端口IP只为了判断哪个节点 调用 private WebMBeanServer server=new WebMBeanServer(); synlog.info(&q ...