右值引用、move函数

转载自:《C++primer》

右值引用

为了支持移动操作,新标准引入了一种新的引用类型----右值引用(rvalue reference),所谓右值引用就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用,右值引用有一个重要的性质----只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值,类似任何引用,右值引用也是某个对象的另一个名字,对于常规引用(左值引用),不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式,右值引用有着完全相反的特性,我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上。

	int i = 42;
	int& r = i;				//正确:r引用i
	int&& rr = i;			//错误:不能将一个右值引用绑定到一个左值上
	int& r2 = i * 42;		//错误:i*42是一个右值
	const int& r3 = i * 42; //正确:我们可以将一个const的引用绑定到一个右值上
	int&& rr2 = i * 42;     //正确:将rr2绑定到乘法结果上

返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值。我们不能将一个左值引用绑定到这类表达式上,但我们可以将一个const的左值引用或者一个右值引用绑定到这类表达式上。

左值与右值

左值持久,右值短暂。左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中闯将的临时对象。
由于右值引用只能绑定到临时对象,所有它有以下特性:
1.所引用的对象将要被销毁
2.该对象没有其他用户
这俩个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。

变量是左值,变量可以看做只有一个运算对象而没有运算符的表达式,变量表达式也有左值/右值属性,变量表达式都是左值,带来的结果就是,我们不能将一个右值引用绑定到一个右值引用类型变量上

	int&& rr1 = 42;  //正确:字面常量是右值
	int&& rr2 = rr1; //错误:表达式rr1是左值

标准库move函数

虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型(不推荐),我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用。此函数定义在头文件utility中。

	int&& rr1 = 42;
	int&& rr2 = (int&&)rr1;  //不推荐
	int&& rr3 = std::move(rr1);

move调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它,我们必须认识到,调用move就意味着承诺除了对rr1赋值或销毁它外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何假设(不能使用一个移后源对象的值)

调用限定语std::move,不提供using声明(避免冲突)

标准库move函数和forward函数都是模版函数,在标准库的定义中它们都接受一个右值引用的函数形参在函数模板中,右值引用形参可以匹配任何类型,如果我们的应用程序也定义了一个接受单一形参的move函数,则不管该形参是什么类型,应用程序的move函数都将与标准库的版本冲突,forward函数也是如此。因此move(以及forward)的名字冲突要比其他标准库函数的冲突频繁的多,所以建议使用带限定语的完整版本

std::move的底层实现

标准库move函数是使用右值引用的模板的一个很好的例子,虽然不能直接将一个右值引用绑定到一个左值上,但可以用move获得一个绑定到左值上的右值引用,由于move本质上可以接受任何类型的实参,因此我们不会惊讶于它是一个函数模板。

标准库是这样定义move的:

template<typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

remove_reference(标准类型转换模板):remove_reference模板有一个模板类型参数和一个名为type的(public)类型成员,如果用一个引用类型实例化该模板,则type将表示被引用的类型,例如实例化remove_reference<int&>,则type成员将是int,remove_reference::type脱去引用,剩下元素类型本身,注意type是一个类的成员,而该类依赖于一个模板参数,因此,我们必须在返回类型的声明中使用typename来告知编译器,type表示一个类型。

这段代码中move的函数参数T&&是一个指向模板类型参数的右值引用,通过引用折叠,此参数可以与任何类型的实参匹配,特别是,我们既可以传递给move一个左值,也可以传递给它一个右值

string s1("hi!"), s2;
s2 = std::move(string("bye!")); //正确:从一个右值移动数据
s2 = std::move(s1);				//正确:但在赋值之后,s1的值是不确定的

std::move(string("bye!")): 

  • 推断出的T的类型为string(当向一个右值引用参数传递一个右值时,由实参推断出的类型为被引用的类型)
  • remove_reference用string进行实例化
  • remove_reference<string>的type成员是string
  • move返回的类型是string&&
  • move的函数参数t的类型为string&&

因此这个调用实例化move<string>,即函数 string&& move(string&& t),函数体返回static_cast<string&&>(t),t的类型已经是string&&,因此调用结果就是它所接受的右值引用。

std::move(s1):

  • 推断出T的类型为string&(string的引用,而非普通string)
  • 因此,remove_reference用string&进行实例化
  • remove_reference<string&>的type成员是string
  • move的返回类型仍是string&&
  • move的函数参数t实例化为string& &&,会折叠为string&

因此这个调用实例化move<string&>,即 string&& move(string& t),这正是我们所寻求的----我们希望将一个右值引用绑定到一个左值,此情况下t的类型为string&,cast将其转换为string&&。

左值static_cast到一个右值引用

通常情况下,static_cast只能用于其他合法的类型转换,但是,这里又有一条针对右值引用的特许规则虽然不能隐式地将一个左值转换为右值引用,但我们可以用static_cast显式地将一个左值转换为一个右值引用。对于操作右值引用的代码来说,将一个右值引用绑定到一个左值的特性允许它们阶段左值,截断一个左值是安全的(为什么安全?),而且通过允许进行这样的转换,C++认可这种做法。统一使用std::move使得我们在程序中查找潜在的截断左值的代码变得很容易。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值