C++右值引用简介

我们对引用都不陌生,引用准许我们为已存在的对象创建别名。若访问或修改创建的引用,全部会直接作用到指涉它的对象本体。

C++11之前,只存在一种引用——左值引用(lvalue reference)。
左值指可以在赋值表达式等号左边出现的元素,包括具名对象、在栈数据段和堆数据段上分配的对象、其他对象的数据成员,或一切具有存储范围的数据项(l指的是location)。
右值指只能在赋值表达式等号右边出现的元素,如字面值或临时变量。

左值引用只可以绑定左值,所以以下代码编译失败:

int& i=42;

但一般可以将右值绑定到const左值引用上:

int const& i = 42;

在现实中,代码却要向接受引用的函数传入临时变量,因而早期C++破例特许绑定方式,让参数发生隐式转换:

void print(std::string const& s);
print("Hello!");

C++11接纳了右值引用特性,它只与右值绑定,且声明改为“&&”:

int&& i=42;
int j=42;
int&& k=j;//编译失败

 右值引用是实现移动语义的基础。

移动语义

假设我们预先知道函数接收右值参数,可以自由改变,我们只需移动右值参数而不必复制本体,能省去更多内存操作。考虑一个自定义类,其默认构造函数申请一大块内存:

#include<iostream>
class X
{
private:
	int* data;
public:
	X():data(new int[10000])
	{}
	~X()
	{
		delete[]data;
	}
	X(const X& other) :data(new int[10000])//复制构造函数
	{
		std::copy(other.data, other.data + 10000, data);
	}
	X(X&& other) :data(other.data)//移动构造函数
	{
		other.data = nullptr;
	}
};

本例展示的移动构造函数,按右值引用的方式接收源实例,复制data指针,将源实例的data改为空指针,节约了一大块内存,也省去了复制时间。

若某个对象不再有用处,想通过移动语义移出,则可通过std::move()或static_cast<X&&>转换为右值

X x1;
X x2 = std::move(x1);
X x3 = static_cast<X&&>(x2); 
void do_stuff(X&& x_)
{
	X a(x_);//复制构造
	X b(std::move(x_));//移动构造
}
int main()
{
	do_stuff(X());//正确,X()生成匿名对象
	X x;
	do_stuff(x);//错误,具名对象x是左值,无法与右值引用绑定
}

移动语义在线程库中大量使用,可以取代不合理的复制语义,也可以实现资源转化。std::thread、std::unique_lock<>、std::future<>、std::promise<>和std::packaged_task<>等类无法复制,但含有移动构造函数,可以按转移方式充当函数返回值,在实例间转移资源,std::thread实例的归属权转移就需要移动语义。

若源对象显式移动到另一对象,源对象只会被销毁或重新赋值。按照良好的编程经验,类需确保不变量的成立范围覆盖“移出状态”,以std::string为例,假设它的实例作为数据源参与移动操作,操作完成后,这一实例需保持某种“合法、有效”的状态。(Effective ModernC++29)

许多std::string(少于15个字符)的实现都采用了小型字符串优化(small string optimization), SSO。采用了SSO以后,“小型”字符串会存储在的std::string对象内的某个缓冲区内,而不去使用堆上分配的存储。在使用了基于SSO的实现的前提下,对小型字符串实施移动并不比复制更快。(参考《Effective ModernC++》)

右值引用和函数模版

假设某函数模版,模版参数是接收参数的类型,其自动类型推导机制:若给出左值作为函数参数,模版参数会被推导为左值引用;若给出右值,则推到为无修饰的普通引用。

template<typename T>
void foo(T&& t)
{}

//传入右值
foo(42);                //调用foo<int>(42);
foo(3.14159);           //调用foo<double>(3.14159)
foo(std::string());     //调用foo<std::string>(std::string())

//传入左值
int i = 42;
foo(i);                 //调用foo<int&>(i)

根据函数声明,其参数型别是T&&,传入左值会被解释为“引用的引用”,发生引用折叠(reference collapsing), 所以其函数签名是:void foo<int&>(int& t);

同一个函数模版既能接收左值,也能接收右值,传入左值则对象会被复制到相应线程内部空间,传入右值则会按移动方式传递。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值