右值引用
C++11 的新标准一个主要特性就是可以移动并非拷贝对象的能力
右值引用:必须绑定到右值的引用,通过&&获得右值引用
右值引用的重要特性就是只能绑定到一个将要销毁的的对象
一般而言,一个左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值
不能将右值引用绑定到一个左值上
int i=42;
int &r=i; //正确:r引用了i
int &&rr=i; //错误:不能将一个右值引用绑定到一个左值上
int &r2=i * 42; //错误:i*42是一个右值
const int &r3=i * 42; //正确:我们可以把一个const引用绑定到一个右值上
int &&rr2=i * 42;//正确
返回非引用类型的函数,连同算术,关系,位以及后置递增递减运算符,都生成右值.我们不能将左值引用绑定到这类表达式上,但是可以将一个 const 的左值引用或者一个右值引用绑定到这类表达式上.
左值持久,右值短暂
右值只能绑定到临时对象上:
- 所引用的对象将要被销毁
- 该对象没有其他用户
使用右值的代码可以自由的接管所引用的对象的资源
变量是左值
标准库move函数
虽然不能将一个右值引用直接绑定在一个左值上,但是我们可以显式地将一个左值转换成对应的右值引用的类型,我们还可以通过调用一个名为move的标准库函数来获取绑定到左值上的右值的引用,此函数定义在头文件 utility 上.
int &&rr3=std::move(rr1);//ok
move调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它.调用move意味着:除了对 rr1 赋值或销毁它,我们将不再使用它,在调用move后,我们不能对移后源对象的值做任何的假设.
移动构造函数和移动赋值运算符
类似拷贝构造函数,移动构造函数的第一个参数是该类类型的一个引用,是一个右值引用,与构造函数一样,任何额外的参数都必须有默认实参.
除了完成资源移动外,移动构造函数必须确保移后源对象处于这样一个状态——销毁它是无害的,特别是一旦完成了资源移动,源对象必须不再指向被移动的资源,这些资源的所有权已经归属新创建的对象.
例子:
StrVec::StrVec(StrVec &&s) noexcept
: elements(s.elements),first_free(s.first_free),cap(s.cap)
{
//令s进入这样的状态---对其运行析构函数是安全的
s.elements=s.first_free=s.cap=nullptr
}
移动构造函数不分配任何新的内存:它接管给定的 StrVec 中的内存.在接管内存后,它将给定的对象的指针都置为 nullptr,这样就完成了从给定的内存的移动操作,此对象将继续存在.最终移后源对象会被销毁,意味这将在其上运行析构函数,如果我们忘了改变 s.first_free,则销毁移后源对象就会释放我们刚刚移动的内存.
不抛出异常的移动构造函数和移动赋值运算符必须标记为 noexcept
移动赋值运算符
移动赋值运算符执行与析构函数和移动构造函数相同的工作.与移动构造函数一样,如果不抛出异常,应该声明为 noexcept.
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
//直接检测自赋值
if(this!=&rhs) {
free();//释放已有的元素
elements=rhs.elements;
first_free=rhs.first_free;
cap=rhs.cap;
rhs.elements=rhs.first_free=rhs.cap=nullptr;
}
return *this;
}
移后源必须可析构
合成的移动操作
合成移动操作的条件与合成拷贝操作的条件不相同
与拷贝操作不同,编译器不会为某些类合成移动操作,特别是,当一个类定义了自己的拷贝构造函数和拷贝赋值运算符或者析构函数,编译器不会为它合成移动构造函数和移动赋值运算符
如果一个类没有移动操作,通过正常的函数匹配,类会使用对应的拷贝操作来代替移动操作.
只有当一个类没有定义任何自己版本的拷贝控制成员,且类的非 static 成员都可以移动,编译器才会为它合成移动构造函数或移动赋值运算符
编译器可以移动内置成员,如果一个成员是类类型,且该类有对应的移动操作,编译器也能移动这个成员
与拷贝操作不同,移动构造永远不会隐式定义为删除的函数,但是,如果显式地要求编译器生成=default 的移动操作,且编译器不能移动所有成员,则编译器会将移动操作定义为删除的函数,
移动右值,拷贝左值
移动迭代器
新标准定义了一种移动迭代器适配器.一个移动迭代器通过改变给定的迭代器的解引用运算符的行为来适配此迭代器.一般来说,一个迭代器的解引用运算符返回一个所指向元素的左值,与其他迭代器不同,移动迭代器的解引用运算符生成一个右值引用.
通过标准库的 make_move_iterator 函数将一个普通迭代器转换为一个移动迭代器
不要随意使用移动操作
由于一个移后源对象具有不确定状态,对其调用 std::move 是危险的,当我们调用 move 时,必须绝对确定移后源对象没有其他用户