std::is_nothrow_assignable 是编译期trait,仅检查赋值运算符是否声明为noexcept,反映接口承诺而非实际行为;对内置类型恒为true,需显式noexcept声明且所有成员/基类赋值也满足该约束。

std::is_nothrow_assignable 是什么,能信吗?
它只是编译期类型 trait,不运行、不构造、不调用赋值运算符——只查类型定义里有没有 noexcept 说明。如果类的 operator= 没显式标 noexcept,哪怕函数体空着也返回 false;反之,哪怕函数里写了 throw,只要声明了 noexcept,trait 就返回 true。
所以它反映的是“承诺”,不是“实际行为”。别拿它当运行时异常检测用。
- 它依赖编译器对
noexcept说明的静态解析,和函数模板实例化无关 - 对内置类型(如
int、double)恒为true,因为它们的赋值是平凡且无异常的 - 对未定义
operator=的类(比如只有默认生成的移动赋值),结果取决于编译器是否给默认函数加noexcept(C++11 起默认不加,C++17 起对平凡类型自动加)
怎么写才能让 is_nothrow_assignable 返回 true?
关键在赋值运算符声明:必须显式、无条件地带上 noexcept。光靠函数体里没 throw 不够,编译器不会推导。
struct Good {
Good& operator=(const Good&) noexcept { return *this; }
};
注意这几点:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 不能写
noexcept(true)或noexcept(false)—— 虽然语法合法,但std::is_nothrow_assignable只认无参数的noexcept - 若类有成员变量,所有成员的赋值都得是
noexcept,否则你的operator=即使标了noexcept,也会因隐式调用失败而编译报错(不是 trait 返回 false) - 继承链中基类的赋值也要满足;若基类赋值非
noexcept,你没法绕过它来“强行”标自己的为noexcept
和 std::is_trivially_assignable、std::is_copy_assignable 有什么区别?
三者完全正交:
-
std::is_trivially_assignable<T, U>:检查是否能用 memcpy 级别操作完成赋值(即平凡赋值),要求类型是 trivial 类型,且赋值不涉及用户定义逻辑 -
std::is_copy_assignable<T>:只关心是否存在可访问的拷贝赋值函数,不管它会不会抛异常 -
std::is_nothrow_assignable<T, U>:只关心该赋值操作是否被声明为noexcept,哪怕它其实调用了可能抛异常的成员函数(只要你没声明错)
典型反例:std::vector<T> 通常是 is_copy_assignable 为 true,但 is_nothrow_assignable 为 false(除非 T 的赋值也是 noexcept 且分配器不抛异常)。
实际用在哪儿?别乱套模板约束
它最常见于 SFINAE 或 requires 表达式中,控制容器或算法的优化路径。比如 std::vector::resize 在元素可 noexcept 移动/赋值时,可能避免异常安全的两段式构造。
但要注意:
- 单独用它做
static_assert很危险——你 assert 的是接口承诺,不是实现可靠性 - 和
std::is_nothrow_move_assignable混用时,别默认两者等价;拷贝赋值和移动赋值的noexcept声明是独立的 - 模板库里若用它做分支,记得测试
std::string这类标准类型:它的赋值在 C++17 后是noexcept,但早期标准不是,跨标准版本行为会变
真正难的不是写对这个 trait,而是确保整个赋值链上每个环节的 noexcept 承诺都一致且可维护——一个第三方库的头文件改了 operator= 声明,就可能让你的 is_nothrow_assignable 断言突然失效。
















