移动构造函数必须显式加noexcept以确保标准容器选择移动而非拷贝;需保证移动后对象可析构且可赋值;避免与模板构造函数冲突;所有成员移动操作也须noexcept。

移动构造函数不加 noexcept 会触发异常传播风险
移动构造函数默认可能抛异常,而标准容器(如 std::vector)在扩容时依赖它是否 noexcept 来决定用移动还是拷贝。如果没显式声明 noexcept,编译器按“可能抛异常”处理,容器就会退回到更慢、更耗资源的拷贝路径。
- 只要移动构造函数里调用了可能抛异常的操作(比如
new、std::string的某些操作、或用户自定义逻辑含 throw),就别默认它安全 - 若确定内部只做指针交换、
std::move内置类型、或调用已知noexcept的成员移动构造,就加上noexcept - 检查方式:用
static_assert(std::is_nothrow_move_constructible_v<myclass>);</myclass>在编译期验证
移动后对象状态必须满足“可析构 + 可赋值”底线
移动不是销毁,只是资源转手。被移动的对象仍要能安全析构,且允许后续赋值——否则 std::vector::resize 或异常回滚时会崩。
- 常见错误:只把指针设为
nullptr,但忘了清空其他资源(如文件句柄、引用计数、或 std::unique_ptr 成员) - 正确做法:对所有拥有资源的成员,统一执行“移交所有权 + 置为有效空状态”,例如
ptr_(other.ptr_), other.ptr_(nullptr) - 不要在移动构造里调用
delete或close()—— 那是析构函数的事
避免移动构造和拷贝构造产生歧义(尤其是模板类)
当类同时有模板化构造函数(如接受任意 T&&)和移动构造函数时,编译器可能优先匹配模板,导致移动语义失效甚至无限递归。
- 典型陷阱:
template<typename t> MyClass(T&&)</typename>会捕获所有右值,包括MyClass&&,从而屏蔽掉显式写的MyClass(MyClass&&) - 修复方法:用
std::enable_if排除自身类型的右值,或直接删掉泛型构造,改用MyClass(const MyClass&)+MyClass(MyClass&&)显式重载 - 验证是否生效:用
std::is_move_constructible_v<myclass></myclass>和实际观察 move 是否被调用(加日志或断点)
std::move 不等于“一定会移动”,也不保证异常安全
std::move 只是类型转换,把左值转成右值引用;真正是否移动、是否异常安全,全看目标类型的移动构造函数怎么写。
立即学习“C++免费学习笔记(深入)”;
- 对
int、std::array这类无状态类型,移动和拷贝一样,不抛异常,也无所谓“移动后状态” - 对自定义类,如果移动构造函数没加
noexcept,哪怕写了std::move(obj),容器仍可能拒绝使用它 - 最易忽略的一点:移动构造函数里调用的每个成员移动操作,都得是
noexcept的,否则整个链路就不是异常安全的
std::move 或关键字自动获得的,而是靠你逐层确认每个资源转移动作是否真的不会抛异常,并显式用 noexcept 告诉编译器和标准库。漏掉一个成员、一行注释、一次静态断言,都可能让优化失效或引发未定义行为。








