右值引入的目的是为了对象移动:
因为在很多情况下,对象拷贝会经常发生,但是很多对象在拷贝后就直接被销毁了。这对性能是一个很大损耗。在重新分配内存的时候,从旧的内存将元素拷贝到新的内存中是不必要的。更好的方法是移动元素。
C++11 扩展了右值的概念,分为了纯右值和将亡值。
纯右值:
非引用返回的临时变量;运算表达式的结果;字面常量(“abs”不是,字符串常量是有地址的)
将亡值:与右值引用相关的表达式
左值:是具名的,且有地址
在旧的C++ 标准中是没有直接移动元素的方法,只有拷贝构造函数。为了实现元素移动,引入了【右值引用】的概念。
右值引用的一个重要性质:只能绑定到一个即将销毁的对象。 因此可以自由的将一个右值引用的资源移动到另一个对象中。
而左值是持久的,分配一个内存中的。右值是暂时的,例如一个运算表达式。
左值引用可以绑定右值,但是右值引用不能绑定左值。为了实现对左值的右值引用,可以使用std:: move()将一个左值转换成右值。这样就可以实现对左值的右值引用了。注意:调用move后,就不能对移动后的对象值做出任何假设。可以销毁或者重新赋值。
区分一个左值和右值的便捷方法,就是看能不能对其表达式取地址,如果能,就是左值,不能就是右值。
有了右值引用的概念,就可以设计【移动构造函数】和【移动赋值运算符】
类似于拷贝构造函数的设计,第一个参数是对应类别的一个引用,但是这里是右值引用。同时移动构造函数还必须保证,在资源移动之后,对应的源对象的销毁是无害的。移动构造函数是不分配任何新的内存的。
【复制构造器】接受一个左值,之后还能继续用。【移动构造器】直接偷窃资源,偷窃后的源对象就不能用了。
只有一个类没定义任何自己版本的拷贝控制成员,并且类的每一个非static 成员都是可以移动的,编译器才会为它合成移动构造函数或移动赋值运算符。
移动右值,拷贝左值;如果没有移动构造函数,右值也会被拷贝;
左值和右值的参数类型推导
【左值引用函数参数推导】
当一个函数参数的模板类型参数是一个左值引用,只能传递一个左值。
template <typename T> void f1(T&)
f1(5);// error
但是如果一个函数参数是 const T&,那边就可以传递任何类型的实参,可以绑定一个右值。
【右值引用函数参数推导】
C++有两个例外的绑定规则:
- 当将一个左值传递给函数的右值引用参数时,且此右值引用指向的是模板类型参数,编译器就可以推导为实参的左值引用类型。
- 如果创造了一个引用的引用,那么这些引用就会形成“折叠”,引用会折叠成一个普通的左值引用类型。
将引用折叠规则和右值引用的特殊规则组合在一起,就可以对一个左值调用带模板类型推导的右值引用。
万能引用:万能引用就是发生了类型推导,如果已经确定了类型,如int && 就是右值引用。如果发生了类型推导,那就是万能引用。
C++ 11 中使用了引用折叠 的推导规则。
template<class T>
void bar(T && t){} // 这里的t就是万能引用
auto && z = get_val(); //z 也是万能引用;
万能引用为的就是完美转发。完美转发就是保持实参的实际类型,如果实参是左值,那么在调用函数中继续保持着其左值属性。右值同理。
template <typename T> void f3(T&& val){
T t = val;
}
如果右值调用 f3 如 字面常量 42, T 就是 int。如果是调用左值 i 那么 T 就是 int&。
理解 std:move
template <typename T>
typename reomve_reference<T>::type&& move(T&& t){
return static_cast<typename remove_reference<T>::type &&>(t);
}
其中 reomve_reference 的作用是返回一个数据结构,其中关键字 type 中包含了变量的类型信息。
从一个左值static_cast 到一个右值引用是允许的。