在我们开始讲解std::move之前,先来介绍一个概念:引用折叠。这个概念仅用于 typedef 和 模板类型参数 中。
在模板世界中,T&& 称为Universal Reference,即通用引用。这与普通函数中形参的&&是不同的,希望不要弄混了。
对于模板函数template <typename T> void func(T&& t),对于左值实参,T&& t 推导出的T为string&;对于右值实参,T&& t 推导出的T为string。
对于左值引用X&,具有传染性,即X& &、X& &&和X&& &都折叠成类型X&;
对于右值引用X&&,只有X&& &&才折叠成X&&。
下面我们来实战一下:
template <typename T>
void func(T&& val) {
T t = val;
t++;
if(val == t) { //... }
}
如果传入的是一个右值42,此时T的类型为int,此时if判断永远为false;如果传入的是一个左值整型变量,则T为int&,此时,if判断永远是true。
T&&通常用于两种情况:模板转发或模板重载。对于模板重载,通常使用以下方式进行重载:
template <typename T> void func(T&&);
template <typename T> void func(const T&);
好,下面进入正题。
一、先来看一下C++标准源码中是怎么实现std::move模板函数的。
//<bits/move.h> <utility>
/**
* @brief Convert a value to an rvalue.
* @param __t A thing of arbitrary type.
* @return The parameter cast to an rvalue-reference to allow moving it.
*/
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
其中,用到了std::remove_reference<_Tp>,这是定义在<type_traits>头文件中的。
// <type_traits>
/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
根据模板匹配,最终reremove_reference返回的都是脱去引用后的普通类型。所以,std::remove_reference<_Tp>::type&& 一定是该类型的右值引用。虽然不能隐式将一个左值转换为右值引用,但是我们可以显式地使用static_cast将一个左值转换为右值引用。
C++标准规定,对于具名的右值引用,是一个左值;但是,对于一个无名的右值引用,是一个右值。std::move返回的正是一个无名的右值引用,所以是一个右值。这就是强制把一个左值变为右值引用的函数,当然,该函数也可以作用于右值,返回结果仍然是该右值的引用。
此时,肯定有人会说,既然这样,那为什么我要用std::move函数,我直接使用static_cast强转就OK了。是的,虽然我们可以直接使用static_cast进行右值引用强转,但是,使用标准库move函数是容易得多的方式。而且,统一使用std::move使得我们在程序中查找潜在的截断左值的代码变得很容易。
同时,move的名字冲突要比其他标准库函数的冲突频繁得多。而且,因为move执行的是非常特殊的类型操作,所以程序专门修改函数原有行为的概率非常小。对move,其实还有forward来说,冲突很多,但大多数是无意的,这一特点解释了为什么会使用带限定语的完整版本的原因。通过书写std::move而非move,我们就能明确地知道想要使用的是函数的标准库版本。
二、再来看一下C++标准源码中是怎么实现std::forward模板函数的。
//<bits/move.h> <utility>
// forward (as per N3143)
/**
* @brief Forward an lvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
与move不同的是,forward必须通过显式模板来调用,forward返回该显式实参类型的右值引用。即:std::forward<T>的返回类型是T&&。显然,如果T是int&,则T&&仍是int&。如果T是int&&,则T&&仍是int&&。从而forward会保持实参类型的所有细节,包括const,因为对于引用来说,const都是底层的,都会予以保留。