std::is_nothrow_move_constructible 是编译期类型特征,用于判断类型移动构造函数是否声明为 noexcept;它不保证运行时不抛异常,仅支持编译期决策,如容器扩容时选择移动或拷贝、实现强异常安全的 safe_swap 等。

std::is_nothrow_move_constructible 是什么,什么时候必须用它
它不是运行时检查工具,而是编译期类型特征(type trait),用来回答“这个类型的移动构造函数是否被声明为 noexcept”——仅此而已。你不能靠它“确保”强异常安全性,只能靠它配合静态断言或 if constexpr 做编译期决策,避免在不该移动的地方触发异常回滚。
典型场景是容器扩容(如 std::vector::resize)或异常安全的 swap 实现:如果元素类型不满足 std::is_nothrow_move_constructible_v<t></t>,标准库可能退回到拷贝构造(更慢、且拷贝也可能抛异常),或者直接放弃优化。
怎么写一个真正依赖 nothrow 移动的 safe_swap
手动实现强异常安全的 swap 时,若想优先用移动而非拷贝,就得先确认移动不会抛异常。否则一旦移动中途抛出,原对象可能已部分失效,违反强保证。
- 用
static_assert在编译期堵住非 nothrow 类型:template<typename t> void safe_swap(T& a, T& b) { static_assert(std::is_nothrow_move_constructible_v<t>, "T must be nothrow move constructible for strong exception safety"); T tmp(std::move(a)); // 此行必须不抛 a = std::move(b); // 此行也需不抛(所以还要检查赋值) b = std::move(tmp); }</t></typename> - 更严谨的做法是同时检查移动构造和移动赋值:
std::is_nothrow_move_constructible_v<t> && std::is_nothrow_move_assignable_v<t></t></t> - 注意:即使
T满足该 trait,其成员若含非 nothrow 移动的子对象(比如自定义类没加noexcept),整个类型仍不满足——trait 是递归求值的,但只看声明,不看实现细节
为什么 std::vector 在扩容时会 fallback 到 copy
当 std::vector<t></t> 需要重新分配内存并迁移元素时,标准要求它优先使用移动操作,但前提是 std::is_nothrow_move_constructible_v<t></t> 为 true。否则,为保障强异常安全性(即扩容失败时原 vector 状态完全不变),它会改用拷贝构造 + 析构旧对象的方式——因为拷贝失败时,旧对象仍完好。
- 你可以用
static_assert验证:static_assert(std::is_nothrow_move_constructible_v<:string>); // true(C++11 起)</:string>
- 但对自定义类,若忘了在移动构造函数后加
noexcept,哪怕函数体里实际不抛,std::is_nothrow_move_constructible_v<myclass></myclass>就是false - Clang/GCC 的
-Weffc++或-Wnoexcept可提醒你漏写noexcept
容易被忽略的陷阱:noexcept 是函数声明的一部分,不是实现保证
std::is_nothrow_move_constructible 查的是函数声明中是否写了 noexcept,不是运行时行为。你可以在移动构造函数里 throw,只要声明了 noexcept,程序就调用 std::terminate,而不是传播异常。
- 这意味着:如果某个第三方库的类型声称
noexcept移动,但内部有未捕获的new失败或std::abort(),你的强异常安全假设就崩了 - 不要仅靠 trait 做逻辑分支;对关键路径,应结合文档、源码或单元测试确认实际行为
- 模板代码里用
if constexpr (std::is_nothrow_move_constructible_v<t>)</t>分支时,记得另一分支必须提供同等异常安全等级的备选方案(比如用try { ... } catch(...) { rollback(); throw; })
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











