C++11中提供了移动对象而非拷贝对象的功能。有时候,对象拷贝后就立即销毁了,这时移动对象会大幅提升性能。很多情况下拷贝对象是不必要的,还有IO类及unique_ptr类这些不可拷贝的类对象,但可以移动。
旧C++标准中,没有移动对象的方法,即使没必要拷贝也不得不拷贝。容器中所保存的类必须是可拷贝的。新标准中,容器可以保存不可拷贝的类,只要能被移动即可。标准库容器、string、shared_ptr类支持移动和拷贝,IO类和unique_ptr类可以移动但不能拷贝。
右值引用
为支持移动操作,新标准引入一种新的引用类型——右值引用,就是必须绑定到右值的引用,通过&&而不是&来获取。右值引用的一个重要性质是只能绑定到一个将要销毁的对象,因此我们可以将右值引用的资源移动到另一个对象中。
左值和右值是表达式的属性,左值表达式表示的是对象的身份(内存中的位置),右值表达式表示的是对象的值。(区分左值还是右值,就看能不能用&符号取地址,能取到地址就是左值,不能则是右值)。
与左值引用一样,右值引用也不过是某个对象的别名而已。对于一个常规引用(左值引用),不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。右值引用则相反:可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上。
int i = 42;
int &r = i; //正确, r 引用 i, 左值引用
int &&r = i; //错误,不能将一个右值引用绑定到一个左值上
int &r2 = i * 42; //错误, i * 42 是一个右值
const int &r3 = i * 42; //正确,可以将一个const的引用绑定到一个右值上
int &&rr2 = i * 42; //正确,将rr2绑定到乘法结果上
返回左值引用的函数,连同赋值、下标、解引用和前置递增、递减运算符,都是返回左值的表达式的例子。我们可以将一个左值引用绑定到这些表达式的结果上。
返回非引用类型的函数,连同算术、关系、位以及后置递增、递减运算符,都生成右值。我们不能将一个左值引用绑定到这类表达式上,但我们可以将一个const的左值引用或一个右值引用绑定到这类表达式上。
左值持久,右值短暂
左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。右值引用只能绑定到临时对象,即引用的对象将要被销毁,该对象没有其他使用者。
右值引用指向将要被销毁的对象。因此,可以从绑定到右值引用的对象“窃取”状态,即使用右值引用的代码可以自由接管所引用的对象的资源。
变量是左值,不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。
int &&rr1 = 42; //正确,字面常量是右值
int &&rr2 = rr1; //错误,表达式rr1是左值
标准库move函数
•虽然不能将一个右值引用直接绑定到一个左值上,但可以显式地将一个左值转换为对应的右值引用类型。我们可以通过调用一个名为 move的新标准库函数来获得绑定到左值上的右值引用。头文件utility,
int &&rr3 = std::move(rr1); //ok
调用move就意味着承诺:除了对rr1赋值或销毁它外,我们将不再使用它。调用move后我们不能对移后原对象的值做任何假设。
我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。
与大多数标准库函数的使用不同,对move我们不提供using声明,而是直接使用std::move而不是move,以避免名字冲突。(std::move接收一个右值引用形参,它可以匹配任何类型,如果应用程序也定义了一个move函数,不管类型如何都会冲突。因此,使用std::move来明确要使用的是标准库的move函数)。