移动构造函数签名必须是t(t&&),接收右值引用以窃取资源;需置空源对象防止double-free;必须加noexcept确保容器启用移动语义;若类声明了析构等特殊成员,编译器不合成移动构造。

移动构造函数的签名必须是 T(T&&)
它接收一个右值引用参数,不能加 const(否则无法修改源对象的内部资源),也不能省略 &&(否则就变成拷贝构造)。常见错误是写成 T(const T&) 或 T(T&),前者是拷贝构造,后者甚至无法绑定临时对象。
典型签名示例:
class Buffer {
public:
Buffer(Buffer&& other) noexcept;
};
-
noexcept强烈建议加上——否则std::vector等容器在扩容时可能放弃移动而退化为拷贝 - 参数名不用叫
other,但语义上它代表“即将被掏空的对象” - 不能声明为
explicit(移动构造函数本就不参与隐式转换)
移动构造里要“掏空”源对象,不是复制
核心动作是把 other 的资源(如指针、文件描述符、std::unique_ptr)直接拿过来,再把 other 的对应成员置为安全的空状态(比如指针设为 nullptr),避免析构时重复释放。
错误做法是用 std::move(other.data) 后不置空:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
Buffer(Buffer&& other) noexcept
: data(other.data) { // 危险!other.data 仍指向原内存
// 缺少 other.data = nullptr;
}
正确写法:
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 关键:掏空后归零
other.size = 0;
}
- 如果成员本身支持移动(如
std::string、std::vector),直接用std::move初始化即可,它们自己会处理掏空逻辑 - 原始指针、C 风格资源(
malloc内存、open()文件句柄)必须手动掏空,否则双重释放 - 别在移动构造里调用
delete或close()——那是析构函数的事
编译器可能自动生成移动构造,但有严格条件
如果你没写移动构造,C++11 起编译器可能隐式生成,但前提是:类没显式定义拷贝构造/拷贝赋值/移动赋值/析构函数中的任意一个,且所有成员都可移动。
- 只要写了析构函数(哪怕空实现),编译器就不再自动生成移动构造——这是最常踩的坑
- 写了
= default可以显式启用(但需确保类满足条件):Buffer(Buffer&&) = default; - 若成员含不可移动类型(如
std::mutex),移动构造会被定义为删除(= delete),此时编译报错use of deleted function
测试是否真走了移动构造
光看编译通过没用。加日志或断点确认执行路径:
Buffer(Buffer&& other) noexcept {
std::cout
- 触发场景:返回局部对象、
std::move(x)、容器插入临时对象(如v.push_back(Buffer{})) - 注意:NRVO(返回值优化)可能绕过移动构造,这时看不到日志——不是没走,是被优化掉了
- 用
volatile或强制关闭优化(-O0)辅助验证,但生产环境别依赖此行为
真正难的不是语法,而是判断哪些资源该移动、掏空后状态是否可安全析构——尤其混用裸指针和 RAII 对象时,边界容易模糊。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










