C++ 对象移动
使用:C++11中新增了移动对象而非拷贝对象能力
目的: 对象拷贝后会立即被销毁,移动而非拷贝对象会大幅提升性能
- IO类或
unique_ptr
这样的类,这些类都包含不能被共享的资源(如指针或IO缓存)
标准库容器、string和
shared_ptr
类既支持移动也支持拷贝。IO
类和unique_ptr
类可以移动但不能拷贝
1 右值引用
必须绑定到右值的引用就是右值引用。我们通过
&&
而非&
获取右值引用
- 只能绑定到一个将要销毁的对象(也就是临时值),所以我们可以自由地将右值引用的资源”移动“到另一个对象中。
左值引用: 常规引用
int i = 42;
int &r = i; // 左值引用
int &&rr = 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
函数
显示地将一个左值转换为对应的右值引用类型
int &&rr3 = std::move(rr1); // std::move后的对象不能再次使用了
我们可以销毁一个move后对象,也可以赋予它新值,但不能使用一个移后源对象的值
2 移动构造函数和移动赋值运算符
- 类似于
string
类,如果我们自己的类也同时支持移动和拷贝,那么也能从中受益
定义移动构造函数和移动赋值运算符
- 第一个参数是该类类型的引用(右值引用)
- 任何额外的参数都必须有默认实参
StrVec::StrVec(StrVec&& s) noexcept // 移动操作不应抛出任何异常
// 成员初始化器接管s中的资源
: elements(s.elements), first_free(s.first_free), cap(s.cap)
{
// 令s进入这样的状态--对其运行析构函数是安全的
s.elements = s.first_free = s.cap = nullptr;
}
解析:与拷贝构造函数不同,移动构造函数不分配任何新内存:它接管给定的StrVec
中的内存。在接管内存之后,它将给定对象中的指针都置为nullptr。这样就完成了从给定对象的移动操作。
移动操作、标准库容器和异常
- 除非标准库知道我们的移动构造函数不会抛出异常,否则它会认为移动我们的类对象时可能会抛出异常,不定义
noexcept
,vector重新分配内存时候会调用拷贝而非移动这就是为啥用
noexpect
class StrVec {
public:
StrVec(StrVec&&) noexcept; // 移动构造函数
}
StrVec::StrVec(StrVec &&s) noexcept; /* 成员初始化器 */
移动赋值运算符
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
// 直接检测自赋值
if (this != &rhs) {
free(); // 释放已有资源
elements = rhs.elements; // 从rhs接管资源
first_free = rhs.first_free;
cap = rhs.cap;
// 将rhs至于可析构状态
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
\