std::is_nothrow_move_assignable 是类型特性,检查类型的移动赋值运算符是否被声明为 noexcept 且编译器可确认不抛异常;它用于编译期判断,影响容器重分配时选择移动还是拷贝。

std::is_nothrow_move_assignable 是什么,它检查什么
它检查一个类型的移动赋值运算符是否被声明为 noexcept,且编译器能确认该操作不会抛出异常。不是检查“实际运行时是否抛异常”,而是检查“是否被承诺不抛异常”。如果类没显式定义移动赋值,编译器合成的版本只有在所有成员和基类的移动赋值都 noexcept 时,才被标记为 noexcept。
怎么用它做编译期判断
直接在模板约束、static_assert 或 if constexpr 中使用:
struct Good {
std::string data;
Good& operator=(Good&&) noexcept = default; // 合成的会是 noexcept(因为 std::string::operator= && 是 noexcept)
};
<p>struct Bad {
std::vector<int> v;
Bad& operator=(Bad&&) noexcept { return *this; } // 显式写了 noexcept,但 vector 的移动赋值其实可能抛(比如内存分配失败),不过标准要求它是 noexcept,所以实际仍是 safe
// 注意:C++17 起 std::vector::operator=(&&) 是 noexcept,但早期实现可能不满足
};验证方式:
-
static_assert(std::is_nothrow_move_assignable_v<Good>);—— 通过 -
static_assert(!std::is_nothrow_move_assignable_v<std::mutex>);—— 通过(std::mutex移动赋值未定义或非 noexcept) - 若类含非 noexcept 移动赋值的成员(如自定义类型未标
noexcept),即使你写了= default,整个类也会是false
常见误判场景和坑
容易以为“没写 throw() 就是 unsafe”,但 C++11 后 noexcept 是隐式默认的——只要没显式加 noexcept(false) 或抛异常语句,编译器就按 noexcept 推导。真正危险的是:
立即学习“C++免费学习笔记(深入)”;
- 成员有非 noexcept 的移动赋值(例如手写的
T& operator=(T&&) { /* 忘了写 noexcept */ }) - 基类移动赋值不是
noexcept - 用了
std::move但源对象析构可能抛异常(std::is_nothrow_move_assignable不管析构!只管赋值操作本身) - 在容器中(如
std::vector::resize)触发重分配时,若元素类型不是noexcept移动赋值,可能降级为拷贝(影响性能)
和 std::is_trivially_move_assignable 的区别
std::is_trivially_move_assignable 要求移动赋值是平凡的(trivial):即编译器合成、无用户定义逻辑、不调用任何非平凡函数;而 std::is_nothrow_move_assignable 只关心是否 noexcept,允许用户定义、非平凡实现,只要它承诺不抛异常就行。
例如:std::unique_ptr<int> 是 noexcept 移动赋值(返回 true),但不是 trivial(因为要置空原指针),所以 std::is_trivially_move_assignable_v<std::unique_ptr<int>> 是 false。
真正关键的不是“能不能移动”,而是“容器在 realloc 时敢不敢放心用移动”——这时候看的正是 std::is_nothrow_move_assignable。


















