移动赋值运算符必须是带右值引用参数的成员函数,返回MyClass&且声明noexcept,需“掏空”源对象并保持其可析构,否则导致double-free或未定义行为。

移动赋值运算符必须是成员函数且带右值引用参数
移动赋值运算符不能是全局函数,也不能用 const 左值引用——否则它就退化成拷贝赋值。标准写法是 MyClass& operator=(MyClass&& other),返回类型必须是 MyClass&,以便支持链式赋值(如 a = b = c)。
常见错误是漏掉 && 写成 operator=(const MyClass& other),这会覆盖不了默认的拷贝赋值,导致移动语义不生效;或者返回 void,破坏赋值表达式合法性。
- 必须声明为
noexcept(强烈建议),否则容器(如std::vector)在扩容时可能放弃移动而改用拷贝 - 如果类有虚函数或继承关系,移动赋值需显式处理基类:调用
Base::operator=(std::move(other)) - 不要在移动赋值里调用
delete或释放资源前检查this == &other——移动操作天然不自赋值,自赋值属于未定义行为,无需防护
移动赋值要“掏空”源对象并保持其可析构
移动后,other 仍必须处于有效但未指定状态(valid but unspecified state),即能安全调用析构函数,也能被再次赋值。典型做法是把源对象的指针置为 nullptr、长度置为 0、句柄置为 INVALID_HANDLE_VALUE 等。
例如管理动态数组的类:data_ 指针从 other.data_ 接管后,必须立刻让 other.data_ = nullptr,否则 other 析构时会 double-free。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 切忌只复制指针而不置空源对象——这是最常见的崩溃根源
- 若类持有多个资源(如文件句柄 + 缓冲区指针),所有资源都得“移交+清零”,缺一不可
- STL 容器(如
std::string、std::vector)的移动赋值已实现此逻辑,直接用std::move(other.member)即可
移动赋值和拷贝赋值不能共存于同一作用域而无重载区分
编译器不会自动为你生成移动赋值,一旦你声明了任何拷贝/移动构造或赋值,编译器就不再合成移动赋值(C++11 起)。如果你写了 operator=(const MyClass&),又没写 operator=(MyClass&&),那 obj = std::move(another) 实际调用的是拷贝赋值——性能掉回原点。
- 启用移动语义前,先删掉手动写的拷贝赋值(除非真需要深拷贝逻辑),让编译器合成或自己补全移动版本
- 用
= default是安全的起点:MyClass& operator=(MyClass&&) = default;,但前提是所有成员都支持移动(比如不含std::array这种不移动只拷贝的类型) - 若类含
const成员或引用成员,移动赋值无法默认生成,必须手写,并注意这些成员不能被重新绑定
调试时如何确认移动赋值真的被调用了
光看代码不等于执行了移动。最直接的办法是在移动赋值里加日志或断点,但更可靠的是观察行为:移动后源对象是否被清空?目标对象是否获得原始资源?有没有内存泄漏或 double-free?
- 用
std::is_move_assignable_v<myclass></myclass>编译期检查是否支持移动赋值 - 在移动赋值函数开头加
assert(other.data_ != nullptr);,再在结尾加assert(other.data_ == nullptr);,快速暴露逻辑漏洞 - 避免在
std::vector::push_back(std::move(x))后继续使用x——即使合法,也容易因忘记“已掏空”而引发后续空指针访问
移动赋值不是“只要写出来就一定高效”,关键在于资源接管是否干净、自赋值是否被排除、异常安全性是否满足(比如 noexcept 声明影响容器行为)。最容易被忽略的是:移动后源对象的状态虽未指定,但必须可析构——这点卡不住编译,却会在运行时崩得毫无征兆。

















