std::move 和 std::forward 解释
一、左值和右值
左值: 一般来说,能在内存中取得其地址, 即是左值。比如正常的某个变量,有变量名,有相应的内存空间。
右值:在内存在无取得其地址的,或者说没有变量名的,即是右值。比如函数传参时,拷贝出来的临时变量,比如 a = b+c 中的 (b+c)结果。
左值对应变量的存储位置,而右值对应变量的值本身。右值如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行利用,从而避免无意义的复制。
二、引用折叠
规则1(引用折叠规则):如果间接的创建一个引用的引用,则这些引用就会“折叠”。
- X& &、X& &&、X&& &都折叠成X&
- X&& &&折叠为X&&
规则2(右值引用的特殊类型推断规则):当将一个左值传递给一个参数是右值引用的函数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板参数类型为实参的左值引用,如
template<typename T>
void f(T&&);
int i = 42;
f(i)
上述的模板参数类型T将推断为int&类型,而非int。
规则3:虽然不能隐式的将一个左值转换为右值引用,但是可以通过static_cast显示地将一个左值转换为一个右值。
std::move
std::move 这个函数名带来了一些混淆。 其实其本质上并没有办法实现“移动”的语义,它的作用是将它的参数通过 static_cast 强转为对应的右值引用。就没其他作用了,一般我们真正实现移动的过程,还是在移动构造函数里完成的。
- move函数的参数T&&是一个指向模板类型参数的右值引用【规则2】,通过引用折叠,此参数可以和任何类型的实参匹配,因此move既可以传递一个左值,也可以传递一个右值
源码如下:
template<typename T>
typename remove_reference<T>::type && move(T&& t)
{
return static_cast<typename remove_reference<T>::type &&>(t);
}
这个语法糖中需要注意的有两点:
- 通过 remove_reference,去除类型的引用。源码也非常简单,模板类中输入参数带&, 输出时去掉该&。这样的话,不管输入的是什么样的内容,都可以避免因为引用折叠规则而导致的类型转换错误。
- 通过 static_cast 函数将输入的内容强转为其对应类型的右值引用。
std::forward
完美转发。 完美转发实现了参数在传递过程中保持其值属性的功能,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。所以常常用在模板里使用。
std::forward只有在它的参数绑定到一个右值上的时候,它才转换它的参数到一个右值。
forward提供两个重载版本, 一个针对左值, 一个针对右值。源码如下:
template<typename _Tp>
constexpr _Tp&&
forward(typename remove_reference<_Tp>::type& __t) _NOEXCEPT
{
return static_cast<_Tp&&>(__t);
}
template<typename _Tp>
constexpr _Tp&&
forward(typename remove_reference<_Tp>::type&& __t) _NOEXCEPT
{
static_assert(!is_lvalue_reference<_Tp>::value,
"can not forward an rvalue as an lvalue");
return static_cast<_Tp&&>(__t);
}
可以看出,根据引用折叠的原理,如果传递的是左值,Tp推断为string&,则返回变成
static_cast<string& &&>,也就是static_cast<string&>,所以返回的是左值引用。
如果传递的是右值,Tp推断为string或string&&,则返回变成
static_cast<string&&>,所以返回的是右值引用。
std::move()和std::forward()对比 | 相同点
- std::move和std::forward只不过就是执行类型转换的两个函数;std::move没有move任何东西,std::forward没有转发任何东西。在运行期,它们没有做任何事情。它们没有产生需要执行的代码,一byte都没有。
- std::forward<T>()不仅可以保持左值或者右值不变,同时还可以保持const、Lreference、Rreference、validate等属性不变;