前言
在很多条件下,一个对象在被复制后立刻被销毁了,在这些情况下,对象的移动比复制更加好,可以提高效率。同时有些类的资源不能共享,例如unique_ptr类,所以需要移动。
1 右值引用
定义:必须绑定到右值,通过&&获得一个右值引用,它们只能绑定到将要销毁的对象。因此我们可以自由的将资源从右值引用移动到另一个对象。
左值和右值是表示的特性,左值表示对象的标识,右值表示对象的值。
右值引用是一个对象的别用。我们无法将常规引用(当我们需要将它们与右值引用区别开来时将其称为左值引用)绑定到需要转换的表达式,文字或返回右值的表达式,右值引用具有相反的绑定属性:我们可以将右值引用绑定到这些类型的表达式,但是我们不能直接将右值引用绑定到左值。
int i = 42;
int &r = i; // ok: r refers to i
int &&rr = i; // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 42; // error: i * 42 is an rvalue
const int &r3 = i * 42; // ok: we can bind a reference to const to an rvalue
int &&rr2 = i * 42; // ok: bind rr2 to the result of the multiplication
1.1 左值和右值区别
左值具有持久状态,而右值是在求值表达式过程中创建的文字或临时对象。因为右值引用只能绑定到临时对象,因此:
- 引用的对象将要销毁
- 该对象没有其他的使用者
因此右值引用能自由的从引用的对象接管资源。
变量表达式有左值和右值特性,变量表达式是左值。
int &&rr1 = 42; // ok: literals are rvalues
int &&rr2 = rr1; // error: the expression rr1 is an lvalue!
我们可以通过move函数,将一个左值变为一个右值
int &&rr3 = std::move(rr1); // ok
当我们调用move后不能再次使用rr1,除非对其进行赋值或销毁它。
2 移动构造和移动赋值
移动本质上是将给定对象的资源偷过来而不是复制,传递的参数是个右值引用,当对象的资源被移走后,原来对象不再指向这些资源,而是由新对象去负责这些资源。在移动过程中,不进行任何资源的分配,所以不会出现异常。我们用noexcept表达这个事实。
StrVec::StrVec(StrVec &&s) noexcept // move won't throw any
exceptions
// member initializers take over the resources in s
: elements(s.elements), first_free(s.first_free),
cap(s.cap)
{
// leave s in a state in which it is safe to run the destructor
s.elements = s.first_free = s.cap = nullptr;
}
但是如果在移动过程中发生了异常的话,就会造成原对象资源的改变,而新对象还没有接管到该资源,这样的话就造成了资源的丢失,而用复制构造就不会产生这个问题,因此我们通过noexcept来解决在移动过程中的问题,显示的告诉library我们的移动构造是安全的。对于已经移除资源的对象,我们要保持它是有效的,只有这样后续才能够进行销毁。
- 仅当一个类未定义其自己的任何复制控制成员并且仅所有数据成员都可以分别移动构造和移动分配时,编译器才构造移动构造函数和移动分配。
- 定义移动构造函数或移动分配运算符的类还必须定义自己的复制操作。否则,默认情况下将删除这些成员。