移动构造函数仅在源对象为纯右值或经std::move显式转换、且类明确定义了形参为t&&的noexcept移动构造函数时才真正起作用,用于高效转移资源而非拷贝。

移动构造函数什么时候真正起作用
它只在对象被“临时创建又马上要扔掉”时触发,比如函数返回局部对象、std::vector扩容时搬元素、用std::move显式标记。编译器不会对普通变量(哪怕加了std::move)自动启用移动——如果类型没定义移动构造函数,就退化成拷贝;如果右值引用参数被绑定到具名变量,也会静默退化为拷贝。
常见错误现象:std::vector<:string> v1 = get_strings();</:string> 看似能移动,但如果get_strings()返回的是const std::vector<:string>&</:string>,那就只能拷贝;再比如写了auto tmp = std::move(x); f(tmp);,tmp是左值,f接收T&&时根本匹配不上。
- 确保源对象是纯右值(如函数返回值)或经
std::move转换后的“可移动标识” - 目标类必须明确定义移动构造函数,且形参为
T&&,不能是const T&& - 移动后源对象进入有效但未定义状态,别再读它的数据成员(除非你手动置空)
怎么写一个安全的移动构造函数
核心是“掏空源对象”,把资源指针/句柄直接拿走,再把源对象的对应字段设为安全初值(比如nullptr、0、std::string())。不调用delete或close,也不做深拷贝。
典型错误:忘记将原对象的指针置空,导致析构时二次释放;或者在移动构造里调用了swap却没处理自赋值;又或者移动后还访问了已被转移的资源。
立即学习“C++免费学习笔记(深入)”;
- 移动构造函数应标记为
noexcept,否则std::vector扩容时可能拒绝使用它(改用拷贝) - 不要在移动构造里抛异常——资源已离开原主,没法回滚
- 若类有多个资源(如缓冲区+文件描述符),全部转移,全部清零
MyString(MyString&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // 关键:掏空后置空
other.size_ = 0;
}
为什么加了移动构造函数还是没提速
常见原因是编译器做了返回值优化(RVO)或命名返回值优化(NRVO),直接绕过构造函数——此时连拷贝都省了,移动自然没机会出场。这不是 bug,是优化。但这也意味着你在性能测试里看到“移动没生效”,很可能只是优化太强,而不是代码写错了。
另一个坑是移动成本本身不低:比如std::array<int></int>移动和拷贝开销几乎一样,因为它是栈上连续内存,没有堆指针可掏;而std::vector、std::string、自定义含new的类才真正受益。
- 用
-fno-elide-constructors关掉 RVO 测试移动是否真被调用 - 优先对“大堆内存 + 少量元数据”的类型实现移动,别给小结构体白忙活
- 检查是否误把移动构造函数写成了
explicit,导致隐式转换路径被堵死
移动和拷贝共存时的重载决议陷阱
当类同时有拷贝构造函数T(const T&)和移动构造函数T(T&&),传入右值时编译器选移动;传入左值时选拷贝。但一旦你把移动构造函数写成T(const T&&),它就几乎永远不会被调用——因为右值更倾向绑定到T&&而非const T&&,而左值根本不能绑定到任何&&。
更隐蔽的问题:如果移动构造函数被= delete,而你又试图移动一个对象,错误信息常是“no matching constructor”,而不是“移动被删除”,容易误判。
- 永远用
T(T&&),别加const - 如果想禁止移动,删掉移动构造函数,或显式
= delete,别留个半残版本 - 注意模板推导:
template<class t> void f(T&&)</class>里的T&&是万能引用,不是右值引用,行为完全不同
noexcept。漏掉其中一环,轻则无效,重则崩溃。










