移动语义是通过转移资源而非复制来避免开销,需显式定义移动构造函数和赋值运算符并置空原对象,std::move仅作类型转换,返回局部变量时应避免使用以免抑制rvo。

移动语义不是“把对象复制得更快”,而是避免复制
移动语义的核心是:当一个对象即将被销毁(比如临时对象、函数返回值、std::move标记的左值),就把它内部的资源(如堆内存指针、文件句柄)直接“拿走”,而不是深拷贝。这省掉了分配新内存、逐字节复制、再释放旧内存的开销。
典型场景是返回大对象、容器插入、异常传递——这些地方原本容易触发不必要的拷贝。但要注意:移动不等于零成本,它只是把昂贵操作从“复制+析构”变成“转移+置空”。如果类没写移动构造函数或移动赋值运算符,编译器会退回到拷贝;如果写了但没正确置空原对象,可能引发双重释放或悬空指针。
- 必须显式定义
MyClass(MyClass&&)和operator=(MyClass&&),编译器不会为有自定义析构函数的类合成移动操作 - 移动后原对象必须处于“有效但未指定状态”(valid but unspecified),比如把
ptr_设为nullptr,否则后续对它的使用(哪怕是析构)可能崩溃 -
std::vector的push_back在扩容时若元素支持移动,就会移动而非拷贝已有元素——这是性能提升的关键路径
什么时候编译器自动调用移动构造,什么时候不?
编译器只在“明显安全”的情况下自动移动:返回局部对象、抛出临时对象、绑定到右值引用参数。但它绝不会对具名变量(哪怕类型是右值引用)自动移动——这是最容易踩的坑。
例如函数返回 std::string 临时对象,编译器会优先调用移动构造;但如果你写 std::string s = "hello"; return s;,默认仍调用拷贝(除非启用 RVO 或显式写 return std::move(s);,而后者通常不推荐)。
立即学习“C++免费学习笔记(深入)”;
- 返回局部变量时,优先触发 RVO(Return Value Optimization),跳过构造/移动;RVO 失败才考虑移动
- 函数参数是
T&&,但传入的是具名变量(如foo(x)),那x是左值,必须用std::move(x)才能进入移动分支 - 容器算法如
std::sort、std::transform内部大量使用移动,前提是元素类型可移动且没有比移动更优的优化(如小字符串优化 SSO)
std::move 不是移动,只是类型转换
std::move 做的唯一一件事:把一个左值强制转成右值引用类型,让重载决议能选中移动版本的函数。它本身不调用任何构造函数,也不修改对象内容,更不保证移动发生。
常见错误是滥用 std::move:对已用过的对象反复移动、对只读对象移动、在返回局部变量时画蛇添足加 std::move(阻止 RVO)。
-
std::move后原变量仍可访问,但内容不可预测——别假设它还“有值” - 对
int、std::array这类 trivial 类型,移动和拷贝等价,加std::move没收益还干扰可读性 - 返回局部变量时写
return std::move(local_obj);会抑制 RVO,实际性能反而下降
自定义类要支持移动,关键在三步:声明、定义、置空
光声明 = default 不够——如果类管理动态资源(比如 char* 缓冲区),默认移动会 bitwise 复制指针,导致两个对象指向同一块内存,析构时 double free。
必须手写移动操作,并确保原对象析构安全。标准做法是“掏空”源对象:把资源指针设为 nullptr,把长度/容量清零。
- 移动构造函数里,用
other.ptr_初始化当前对象,然后立刻置other.ptr_ = nullptr - 移动赋值运算符需先释放当前资源(避免泄漏),再执行同上的“掏空”逻辑
- 如果类有 const 成员或引用成员,无法默认移动,必须手动处理或禁用移动(
= delete)











