【std::move源码剖析】

std::move()

首先,给出std::move函数的源代码:

  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

看起来有点乱,去掉一些编译相关的宏和关键字,"精简后"的代码如下:

template<typename _Tp>
typename std::remove_reference<_Tp>::type&& move(_Tp&& __t)
 { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

  从函数定义可以看出,无论参数是什么类型,最后返回的都是该传入参数的右值引用。其中,不太好理解的是以下两点:
一是函数传入参数的类型为_Tp&&,因为这里的模板参数_Tp的类型是不确定的,所以存在着类型推导,所以这里_Tp&&是万能引用。
二是函数体中remove_reference<_Tp>::type,其作用是去掉传入参数的引用,其后跟上&&,就成了传入参数的右值引用。

1._Tp&& 万能引用

万能引用其实通过两个简单的规则就能理解。

1.类型推导:
  右值引用只能绑定到右值,如int&& ref_right = 10绑定到右值10,但是右值引用是函数模板中的函数参数时(如fun(Tp&& t), 该右值引用也可以绑定到左值 ,但是此时Tp的类型推导为左值引用,而不是左值。而将右值引用绑定到右值引用,则Tp的类型推导为普通类型(非引用)。

2.引用折叠:
  首先,不能直接定义引用的引用,当间接(类型别名和模板参数)出现了引用的引用时,除了右值引用的右值引用,其他的都折叠为左值引用。如int& &&折叠为int&,int&& &折叠为int &,int&& &&折叠为int &&。

//类型别名引用折叠
using Type = int&&void test(Type &)
{
}

  综合以上两条规则,可以给出如下代码:

template<typename _Tp>
void fun(_Tp&& __t)
{
}

int main()
{
	int num1 = 10;
	int& num2 = num1;
	int&& num3 = 20;
	fun(num1);           //num1是左值,但是_Tp推导为int&,而不是int(例外规则),再根据引用折叠的规则, __t的类型推导为int&
	fun(num2);			 //num2是左值引用,_Tp推导为int&,再根据引用折叠的规则, __t的类型推导为int&
	fun(20);			 //20是右值,_Tp将会推导为int,再根据引用折叠的规则, __t的类型推导为int&&
	fun(num3);			 //num3是右值引用,但是_Tp推导为int,而不是int&&,这里不是引用的引用,不需要引用折叠,__t的类型推导为int&&
}

2. std::remove_reference<_Tp>

  所以通过万能引用接收参数后,函数模板中的_t变量不是左值引用就是右值引用。此时再通过remove_reference去掉其引用。
remove_reference的源码如下:

#if __has_builtin(__remove_reference)
  template<typename _Tp>
    struct remove_reference
    { using type = __remove_reference(_Tp); };
#else
  template<typename _Tp>			//主模板
    struct remove_reference
    { using type = _Tp; };

  template<typename _Tp>
    struct remove_reference<_Tp&>  //左值引用偏特化,偏特化也有叫部分具体化的
    { using type = _Tp; };

  template<typename _Tp>
    struct remove_reference<_Tp&&> //右值引用偏特化在上述例子不会被调用,因为之前推导过_Tp的类型只会被推导为int &或者int
    { using type = _Tp; };
#endif

也就是说,remove_reference是一个类模板(区别于函数模板,cpp中结构体也是类,仅成员访问权限不同)。
在没有内建的__remove_reference的情况下(else分支),通过左值引用偏特化的版本,获取到左值引用不带引用的类型,如remove_reference<int &>::type为int;通过主模板的版本,获取到不带引用的类型,如remove_reference<int>::type仍为int。

所以remove_reference<_Tp>::type&&一定是右值引用。但是需要注意尽量不要对纯右值使用move,这会返回一个临时变量的右值引用,可能有引用悬垂问题。尽管在g++与msvc上测试可能可以正常使用,但是最好不要依赖于编译器的实现来“解决”未定义问题。

最后,可以配合如下代码进行测试理解:

#include <iostream>

using namespace std;

//原始模板定义
template<typename _Tp>
struct my_remove_rerference{
    using type = _Tp;
    
    static void print(){
        cout<<"调用原始模板!"<<endl;		
    }
};

//针对左值引用的特化版本
template<typename _Tp>
struct my_remove_rerference<_Tp&>{   
    using type = _Tp;

    static void print(){
        cout<<"调用左值引用!"<<endl;
    }
};

//针对右值引用的特化版本
template<typename _Tp>
struct my_remove_rerference<_Tp&&>{   
    using type = _Tp;

    static void print(){
        cout<<"调用右值引用!"<<endl;
    }
};

template<typename _Tp>
typename my_remove_rerference<_Tp>::type&&
my_move(_Tp&& __t) 						//my_move是对move的仿写,其返回值依然是右值引用,我们需要知道
{ 									    //在my_remove_rerference<_Tp>到底是被
    my_remove_rerference<_Tp>::print();
    // my_remove_rerference<_Tp&>::print();   可以测试Tp到底被推导何种类型1
    // my_remove_rerference<_Tp&&>::print();  可以测试Tp到底被推导何种类型2

    return static_cast<typename my_remove_rerference<_Tp>::type&&>(forward<_Tp>(__t)); 
}


int main()
{
    int left = 10;
    int& left_ref = left;
    int&& right_ref = 20;


	my_move(left);			/*传递左值,my_move函数中的参数:_Tp推导为int&, 
							其返回值my_remove_rerference<_Tp>中_Tp为int&,
							所以调用类模板my_remove_rerference的左值引用版本,
							*/
	my_move(left_ref);		/*传递左值引用,my_move函数中的参数:_Tp推导为int&, 
							其返回值my_remove_rerference<_Tp>中_Tp为int&,
							所以调用类模板my_remove_rerference的左值引用版本
							*/
	my_move(20);		    /*传递纯右值,my_move函数中的参数:_Tp推导为int, 
							其返回值my_remove_rerference<_Tp>中_Tp仍为int,
							所以调用类模板my_remove_rerference的原始模板版本
							*/
	my_move(move(right_ref));		/*传递右值引用,my_move函数中的参数:_Tp推导为int, 
							其返回值my_remove_rerference<_Tp>中_Tp仍为int,
							所以调用类模板my_remove_rerference的原始模板版本
							*/

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值