::move(rr1); // ok
调用了move意味着承诺:除了对rr1赋值或者销毁它以外,我们将不再使用它(不再使用它的值)。
2.移动构造函数、移动赋值运算符
移动操作,表示从给定对象“窃取”资源,而不是拷贝资源。
StrVec::StrVec(StrVec &&s) noexcept // 移动操作不应该抛出任何异常 // 成员初始化接管 s 中的资源 : element(s.element), first_free(s.first_free), cap(s.cap) { // 必须让 s 进入这样的状态-----对其运行析构函数是安全的。 // 因为,其后s 源对象会被销毁,也就是执行析构函数,如果s.first_free 还指向原来的资源 // 那么,移动的内存就会被销毁,这不是我们所想要的。 s.element = s.first_free = s.cap = nullptr; // 源对象必须不再指向被移动的资源 }
移动赋值运算符必须要正确处理自赋值(赋值运算符都需要考虑这一点)
StrVec &StrVec::operator=(StrVec &&rhs) noexcept { // 检测自赋值 if (this != &rhs) { free(); // 释放已有的元素 elements = rhs.elements; // 从rhs接管资源 first_free = rhs.frist_free; cap = rhs.cap; // 将rhs置于可析构状态 rhs.elements = rhs.first_free = rhs.cap = nullptr; } }
注意:
移源后对象必须可析构,另外,移动构造函数默认不能由编译器合成,但是如果类中的每个非static数据成员都是可移动的,编译器就可以为它合成移动构造函数或移动赋值运算符。其中,内置类型可以移动,string定义了自己的移动操作。
如果一个类有一个可用的拷贝构造函数而没有移动构造函数,则其对象是通过拷贝构造函数来”移动”的。
class HasPtr { public: // 移动构造函数 HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) { p.ps = 0; } // 赋值运算符既是移动赋值运算符,也是拷贝赋值运算符 HasPtr &operator=(HasPtr rhs) { swap(*this, rhs); return *this; } }; hp = hp2; // hp2 是一个左值,使用拷贝构造函数来拷贝 hp = std::move(hp2); // 移动构造函数移动hp2
最后建议:不要随意使用移动操作,因为移后源对象具有不确定的状态,对其调用std::move是危险的。当我们调用move函数时,必须绝对确认移后源对象没有其他的用户。