本文首发于我的公众号:码农手札,主要介绍linux下c++开发的知识包括网络编程的知识同时也会介绍一些有趣的算法题,欢迎大家关注,利用碎片时间学习一些编程知识,冰冻三尺非一日之寒,让我们一起加油!
前言
今天在网上冲浪的时候无意中看到了lambda的表达式,突然想到一个问题,lambda表达式的实现原理是什么呢,今天,我们就来做个简单的分析,了解一下c++在提供lambda表达式背后的实现机制
例子
1 | std::vector<int> vec = {1,2,3}; |
这段代码的输出结果相当大家都不会觉得奇怪,vec和vec2的size大小都是3,那么接下来看这个例子:
1 | std::vector<int> vec = {1,2,3}; |
这次相信很多人已经没办法理解了,那么接下来我们来解释一下
move的本质
关于move,我们需要知道的最重要的事情就是move一定会成功吗?
显然不是,让我们来看看move的简易实现代码:
1 | template<typename T> |
从代码可以看出来,move的本质是调用了static_cast做了一层强制转换,强制类型转换的目标类型是remove_reference_t<T>&&,关于remove_reference_t,是为了去除类型本身的引用,比如参数是左值引用。简单来说,move的目的是将对象强制类型转换为右值引用。那么使用move实现移动语义的本质又是什么呢,很简单,本质上就是移动构造函数或者移动赋值运算符,因此,对于下面这种例子:
A a = std::move(b);//假定A定义了移动构造函数
那么就会调用A的移动构造函数来构造a,实现了移动语义。
那么问题来了,什么情况下move会失效呢,其实很简单,如果move之后的变量类型不是&&,那么就不会触发移动构造函数或者移动构造运算符,譬如下面这个例子:
const std::string str = “hello world”;
std::string str2(std::move(str));
这种情况下,对str调用move函数得到的变量类型是const std::string&& ,所以移动构造函数实际上不会起作用,反而会触发拷贝构造函数(由完美转发)
接下来我们来解释为什么两个例子有不同的结果,首先我们需要了解lambda的原理
lambda闭包原理
对于c++的lambda,编译器会将转换为一个匿名的闭包类(闭包简单来理解就是带有上下文,也就是有状态的),所以对于开始例子中给出的一个lambda,实际上转化为下面的代码:
1 | ///before translation |
需要注意的是,生成的闭包类的operator()函数默认被const修饰,也就是说operator()函数是const成员函数,所以对成员变量v1调用std::move的结果就会引发上面所说的move失效的情况,转变出来的类型是const string&&,所以移动构造函数并不会被调用,反而调用的是拷贝构造函数
总结
到这里,大家应该可以理解第一个例子中的情况,在第二个例子中我们引入了mutable,这样的话闭包类生成的operator()函数不会被const修饰,所以我们调用move就会成功匹配到移动构造函数,实现了移动语义
参考文章:
c++闭包理解
c++中mutable关键字
C++中lambda表达式详解与原理分析
C++ lambda 内 std::move 失效问题的思考