c++11新增类型-右值引用,标记为&&。
基础:
什么是左值什么是右值:
- 左值是指存储在内存中,有明确存储地址(可取地址的)数据。
- 右值是指可以提供数据值的数据(不可以去地址)
右值分类:
c++11中右值又可以分为两种:
- 纯右值:是一个临时对象,通常实在表达式中生成的,没有名称且无法取地址,他们的生命周期非常短,通常表达式结束后就被销毁。
- 将亡值:与右值引用相关的表达式,比如T&&类型函数的返回值,std::move的返回值。
右值引用:
右值引用表意就是对与右值进行引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到他。无论是左值引用还是右值引用都必须要立即初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用的声明,该右值又"重获新生“,其生命周期与右值引用类型变量的生命周期一样,只要该变量还活着,该右值零时量将会一直存活下去。
性能优化:
在c++中进行对象赋值操作的时候,很多情况下会发生对象之间的深拷贝,如果堆内存很大,这个拷贝代价也就非常大,在某些情况下,如果想要避免对象的深拷贝,就可以用右值进行性能优化:
通过上述的内存地址可以看出,Test t = getObj()的时候调用了拷贝构造函数对返回的临时对象进行了深拷贝得到了对象t,在调用函数中创建的对象虽然进行了内存的申请操作,但是没有使用完就释放掉、如果能够使用零时对象已经申请的资源,既能节省资源,还能节省资源申请和释放时间,如果要执行这样的操作就需要使用右值引用,右值引用就有移动语义,移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象这样可以减少不必要的零时对象的创建、拷贝以及销毁、可以大幅度提高c++应用程序的性能。
下面改动:
通过修改,在上面的代码中给类添加了移动构造函数,这样在进行Test t = getObj()操作的时候并没有调用拷贝后遭函数进行深拷贝,而是调用了移动构造函数,在这个函数中进行了浅拷贝,没有对临时对象进行深拷贝,提高了性能。
在测试过程中getObj()的返回值就是一个将亡值,也就是说一个右值,在进行赋值操作的时候,如果=右边是一个右值,那么移动构造函数就会被调用,移动构造中使用了右值引用,会将临时对象中的堆内存地址的所有权转移给对象t,这块内存被成功续命,因此t对象中可以继续使用这快内存。
注:对于需要动态申请大量资源的类,应该设计移动构造函数,以提高程序效率,需要注意的是,我们一般在提供移动构造函数的同时,也会提供常量左值引用的拷贝构造函数,以保证移动不成还可以使用拷贝构造函数。
&&的特性
在C++中,并不是所有情况下&&都代表是一个右值引用,具体的场景体现在模板和自动类型推导中,如果是模板参数需要指定为T&&,如果是自动类型推导需要指定为auto&& ,在这种场景下&&被称为作未定义的引用类型,另外还有一点需要额外注意const T&&表示一个右值引用,不是一个未定引用类型。
auto&& 和T&&推导规则:
- 通过右值推导T&&或者auto&&得到的是一个右值引用类型。
- 通过非右值(右值引用,左值,左值引用,常量右值引用,常量左值引用)推导T&&或者auto&&得到的是一个左值引用类型(由于右值引用和常量右值引用也是一种别名,在使用的时候是左值,所以推导出的左值引用类型)。
举比较容易误解的例子
这几个可能比较误解的,符合上述红色字体的规则。 也就是说除了是右值才可以自动推导出是右值引用其余的都是左值引用。
总结:
- 左值和右值独立于他们的类型的,右值引用类型可能是左值也可能是右值。
- 编译器会将已命名的右值引用视为左值,将未命名的右值引用视为右值。
- auto&&或者函数参数类型自动推导的T&&是一个未定义的引用类型,他可能是左值引用也可能是右值引用类型,取决于初始化的值类型。
- 通过右值推导T&&或者auto&&得到的是一个右值引用类型吗,其余都是左值引用类型。
--------------------------------------------------------------------------------------------------
上述如有错误欢迎大家批评指正