1.右值引用
&&x
"&&"符号是右值引用符号
相对于左值引用来说,右值引用只能绑定到一个结果,一个临时量、常量上面。而左值引用一般是绑定到一个对象上面,进行地址、身份的绑定。(*表达式是一个左值,同样,变量可以看作没有运算符的表达式)
2.标准库move函数
通过utility中 move函数,可以获得变量的右值引用
int &&rr3 = std::move(rr1);
3.移动构造函数
如果让自己的类支持移动,而不是拷贝,可以在实际使用中以及特定的条件下节省大量的空间。
因为“移动”的核心意义是“接管” 而不是复制:
StrVec::StrVec(strVec &&s) noexcept :
elements(s.elements),first_free(s.first_free),cap(s.cap)
{
s.elements = s.first_free = s.cap = nullptr;// 使原来的类内指针空置,这样析构函数就不会销毁被“接管”的资源
}
4.移动构造函数和noexcept
移动构造函数及移动构造运算符应声明为 noexcept的
StrVec(Strvec &&) noexcept;
是为了安全以及避免标准库容器(?编译器) 做出额外的工作:
例如vector::push_back 异常时,会进行重新分配空间,并进行拷贝(移动),
但是在我们自定义的类中,如果在移动过程中,移动部分后出现了异常,那么旧的类已经改变,而新的类没有完整生成。会出现不安全的情况?
所以在移动函数中只有声明为noexcept ,使用标准库容器时,才支持移动操作,否则考虑到安全 问题,标准库容器,只能会进行拷贝,而不能进行移动。
5.移动后的原对象,需要保证可以析构
6.拷贝并交换赋值运算符
class HasPtr
{
public:
HasPtr(HasPtr &&p) noexcept : ps(p.ps),i(p.i) { p.ps = 0;} // 移动构造函数
HasPtr& opeator=(HasPtr rhs)
{ swap(*this,rhs); retrun *this;}
}
上面的赋值运算符的参数,HasPtr rhs 是非引用的,所以会进行拷贝初始化,而拷贝初始化使用 拷贝构造函数或者 移动构造函数(根据实参的类型而定)。
当实参是左值的时候,rhs会被拷贝初始化;
当实参是右值的时候,rhs会被移动初始化;
当rhs被初始化之后,通过swap操作,进行交换,这样也能避免额外的开销。而rhs交换离开作用域后,就会被销毁。
7.移动迭代器
通过make_move_iterator 函数将一个普通迭代器,转换为移动迭代器。
对移动迭代器解引用会得到一个右值引用。