最近在写C++时,有这样一个代码需求:在lambda中,将一个捕获参数move给另一个变量。 看似一个很简单常规的操做,然而这个move动做却没有生效。c++
具体代码以下:bash
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时并无按预期调用移动构造函数,而是调用了拷贝构造函数。ui
为何会形成这个问题呢, 咱们须要结合std::move
和lambda
的原理看下。(最终的解决方案能够直接看这里)spa
对于std::move,有两点须要注意:code
对于第二点来讲,答案显然是不能。这也是本文的问题所在。那么std::move其实是作了什么事情呢?对象
对于std::move,其实现大体以下:rem
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本质上是将对象强制转换为了右值引用。get
那么,为何咱们一般使用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的工做原理。
对于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 = [=](){
auto vec2 = std::move(vec);
std::cout << vec.size() << std::endl; // 输出:0
std::cout << vec2.size() << std::endl; // 输出:3
};
复制代码
代码能够在wandbox运行。