移动构造函数必须用std::move转移独占资源并置源对象为安全状态,否则导致野指针或未定义行为;移动赋值需处理自赋值和资源释放;默认移动操作仅在无自定义析构/拷贝操作时由编译器生成。

移动构造函数里必须用 std::move 转移成员,不能直接赋值
常见错误是把移动构造写成浅拷贝:比如直接 other.data = nullptr,却忘了对当前对象的 data 成员做初始化。这会导致未定义行为——新对象的 data 是野指针。
正确做法是先将当前对象的资源置为安全状态(通常是 nullptr 或空状态),再用 std::move 把源对象的资源“拿过来”:
MyClass(MyClass&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 归还源对象所有权
other.size = 0;
}
- 所有裸指针、
std::unique_ptr、动态分配的缓冲区等独占资源,都必须显式转移 - 内置类型(
int、double)可直接拷贝,无需std::move - 务必加
noexcept——否则容器(如std::vector)在扩容时可能退化为拷贝而非移动
移动赋值运算符要处理自赋值和资源泄漏
很多人只写 swap 或直接覆盖,结果在 a = std::move(a) 时崩溃,或旧资源没释放就丢了指针。
标准写法是“析构 + 移动构造”的组合,或更稳妥的手动清理:
MyClass& operator=(MyClass&& other) noexcept {
if (this == &other) return *this; // 自赋值检查
delete[] data; // 释放当前资源
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
return *this;
}
- 自赋值检查不是可选的——虽然移动后源对象通常不再使用,但语言不禁止
std::move(x)后继续用x - 不能省略
delete[] data:否则每次移动赋值都会泄漏内存 - 如果类里有
std::vector或std::string这类已实现移动语义的成员,它们会自动移动,不用手动干预
什么时候可以不写移动操作?看编译器是否生成默认版本
如果你的类只有普通成员(如 int、std::string、std::vector),且没声明析构函数、拷贝构造/赋值,那么编译器会隐式生成移动构造和移动赋值(C++11 起)。
但只要满足以下任一条件,编译器就**不会**生成默认移动操作:
- 声明了自定义析构函数
- 声明了拷贝构造函数或拷贝赋值运算符
- 显式删除了移动操作(
= delete)
此时若还想支持移动,就必须手动定义,否则 std::move(obj) 会退化为调用拷贝构造——性能掉一大截,尤其对大对象。
移动后源对象的状态必须明确且可析构
移动操作完成后,源对象(如 other)仍必须处于有效但未指定的状态:能安全析构、能重新赋值、不能访问其已转移的资源。
典型错误包括:
- 移动后没清空
data,导致析构函数重复delete[] data - 移动了
std::unique_ptr却忘了它本身已被置为nullptr,误以为还能用 - 在移动赋值中漏掉某个资源成员(比如新增了一个
buffer字段,但没在移动逻辑里处理)
最保险的做法是:每个独占资源成员,在移动后都设为“空值”,并确保析构函数能容忍这种状态。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











