C++学习笔记:右值引用

C++11中引入了右值引用,可以避免无谓的复制,提高了程序性能。

一、概念

C++11增加了一个新的类型,称为右值引用(R-value reference),标记为T&&。

在介绍右值引用前,我们先来复习一下C++中的右值和左值。

左值:是指表达式结束后依然存在的持久对象。

右值:是指表达式结束时不再存在的临时对象。

提问:如何区分左值和右值?

一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。

所有的具名变量或对象都是左值,而右值不具名。

提问:什么是右值?   

在C++中,右值由两个你概念构成,一个是将亡值(expiring value),另一个则是纯右值(PureRvalue)。

举例将亡值:

将要被移动的对象,T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值。

举例纯右值:

非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值。

小结:

C++11中所有的值都属于左值、将亡值、纯右值三者之一,将亡值和纯右值都属于右值。

区分表达式的左右值属性有一个简便的方法:若可对表达式用&取地址符,则为左值,否则为右值。

举例: 

int i = 0;

解析:i是左值,0是字面值,就是右值。

因为i可以被引用,0不可以被引用。字面值都是右值。

二、右值引用&&的特性

右值引用就是对一个右值进行引用的类型。

因为右值不具名,所以我们只能通过引用的方式找到它。

无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。

通过右值引用的声明,该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样,只要该变量存在,右值临时量将会一直存活下去。

2.1 返回值优化

在没有返回值优化的情况下,拷贝构造函数调用了两次。

如果我们通过右值引用,可以减少一次拷贝构造和一次析构,原因在于右值引用绑定了右值,让临时右值的声明周期延长了。

我们可以利用这个特点做一些性能优化,即避免临时对象的拷贝构造和析构。

2.2 常量左值引用和右值引用

A&& a = GetA();//右值引用
const A& a = GetA();//常量左值引用

上述两种方式输出的结果是一样的。 

因为常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值。

需要注意的是普通左值引用不能接受右值。

A&& a = GetA();
//上面的会编译错误,因为非常量左值引用只能接受左值。

2.3 T&&

实际上T&&并不是一定表示右值,它绑定的类型是未定的,即可能是左值又可能是右值。

template<typename T>
void f(T&& param);
f(10);   //10是右值
int x=10;
f(x);    //x是左值

从上述例子可以看出,param有时是左值,有时是右值,因为在上面的例子中有&&,这表示param实际上是一个未定的引用类型。

这个未定的引用类型称为universal references(可以认为它是一种未定的引用类型),它必须被初始化。

它是左值还是右值引用取决于它的初始化,如果&&被一个左值初始化,它就是一个左值;

如果它被一个右值初始化,它就是一个右值。

只有当发生自动类型推导时&&才是一个universal references。

universal references仅仅在T&&下发生,任何一点附加条件都会使之失效,而变成一个普通的右值引用。

因此,上面的T&&在被const修饰之后就成为右值引用了!

2.4 引用折叠

由于存在T&&这种未定的引用类型,当它作为参数时,有可能被一个左值引用或者右值引用的参数初始化,这种经过类型推导的T&&类型,相比右值&&引用会发生类型的变化,这种变化被称为引用折叠。

C++11中的引用折叠规则如下:

(1)所有的右值引用叠加到右值引用上仍然还是一个右值引用。

(2)所有的其他引用类型之间的叠加都将编程右值引用。

左值或者右值是独立于它的类型的,右值引用可能是左值也可能是右值。 

int && val1 =x;
//val的类型是int&&
auto && val2 = val1;
//auto && 最终会被推导为int&

2.5 std::move

提问:如果希望把一个左值赋给一个右值引用类型该怎么做呢?

用std::move

decltype (w1) && v2 = std::move(w2);

std::move可以将一个左值转换为右值。

三、右值引用优化性能,避免深拷贝

对于含有堆内存的类,我们需要提供深拷贝构造函数,如果使用默认构造函数,会导致堆内存的重复删除。

默认构造函数是浅拷贝,在析构的时候会导致重复删除该指针,正确的做法是提供深拷贝构造函数。

3.1 移动函数

移动构造函数只能是将临时对象的资源做了浅拷贝,不需要对其进行深拷贝,从而避免了额外的拷贝,提高性能。

这就是所谓的移动语义。

右值引用一个重要目的是用来支持移动语义的。

移动语义可以将资源(堆、系统对象等)通过浅拷贝方式从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,可以大幅度提高C++应用程序的性能,消除临时对象的维护对性能的影响。

3.2 小结

有了右值引用和移动语义,在设计和实现类时,对于需要动态申请大量资源的类,应该设计右值引用的拷贝构造函数和赋值函数,以提高应用程序的效率。

需要注意的是,我们一般在提供右值引用的构造函数的同时,也会提供常量左值引用的拷贝构造函数,以保证移动不成还可以使用拷贝构造。

需要注意的一个细节是,我门提供移动构造函数的同时也会提供一个拷贝构造函数,以防止移动不成功的时候还能拷贝构造,使我们的代码更安全。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值