C++如何使用std::is_nothrow_swappable检测无异常交换?(异常安全swap)

1次阅读

std::is_nothrow_swappable 是编译期 trait,仅检查 swap 函数是否显式或隐式声明为 noexcept,不验证实际行为;需为自定义类型提供 adl 可见的 noexcept swap,推荐用 noexcept(noexcept(…)) 推导。

C++如何使用std::is_nothrow_swappable检测无异常交换?(异常安全swap)

std::is_nothrow_swappable 是编译期判断,不是运行时检测

它只看 swap 函数的 noexcept 说明符是否被显式或隐式标记为 noexcept,不关心实际交换逻辑会不会抛异常。比如你手写了一个没加 noexceptswap,哪怕里面只有一行 std::swapstd::is_nothrow_swappable_v<t></t> 也会是 false

常见错误现象:static_assert(std::is_nothrow_swappable_v<mytype>, "must be nothrow swappable");</mytype> 突然失败,但你确认成员都支持无异常交换——大概率是漏写了 swapnoexcept 声明。

  • 必须为自定义类型提供 swap 函数,并显式标注 noexcept(推荐用 noexcept(noexcept(...)) 形式推导)
  • 依赖 ADL 的 swap:确保你提供的 swap 在对应命名空间,且签名正确(通常为 void swap(T&, T&) noexcept
  • 若未提供自定义 swap,会退回到 std::swap,而 std::swapnoexcept 性取决于其内部调用的移动构造/移动赋值是否 noexcept

怎么写一个真正 nothrow 的 swap 函数

光声明 noexcept 不够,得保证函数体里所有操作都不抛异常。c++11 起,std::swap 默认是 noexcept 的,但前提是模板参数满足 std::is_nothrow_move_constructible_vstd::is_nothrow_move_assignable_v

使用场景:实现强异常安全的容器(如 vector::swap)、配合 std::move_if_noexcept、或作为其他 traits(如 std::is_nothrow_move_constructible)的间接依赖。

立即学习C++免费学习笔记(深入)”;

  • 对类成员逐个 swap,每个都要确保其 swapnoexcept 的;基础类型、std::unique_ptrstd::Array 等默认满足
  • 避免在 swap 里调用可能抛异常的操作:比如 newstd::String::resize、任何用户自定义函数(除非你 100% 确认它 noexcept
  • 推荐写法:
    void swap(MyType& other) noexcept(noexcept(std::swap(a, other.a)) && noexcept(std::swap(b, other.b))) { std::swap(a, other.a); std::swap(b, other.b); }

std::is_nothrow_swappable_v 为 false 的典型原因

不是“交换会抛异常”,而是“编译器无法证明它不会抛”。这很关键——它影响的是泛型代码的分支选择(比如 std::vector::resize 内部是否敢用移动而非复制),而不是运行时报错。

常见错误现象:明明所有成员都 trivially swappable,但 std::is_nothrow_swappable_v<mystruct></mystruct>false;或者在模板中用了 static_assert 却过不了。

  • 类里有用户定义的非 noexcept 移动构造函数或移动赋值运算符(即使没显式写 throw(),缺省也不是 noexcept
  • 基类或成员的 swap 没提供,或提供了但没 noexcept 声明
  • 使用了 std::tuplestd::pair 包含非 nothrow 类型:它们的 swapnoexcept 的当且仅当所有元素的 swap 都是 noexcept
  • 注意:空类、POD 类、只含 int/double 成员的类,如果没自定义任何特殊成员函数std::is_nothrow_swappable_v 通常是 true(靠 std::swap 的默认实现)

别把它和 std::is_swappable_v 混用

std::is_swappable_v<t></t> 只检查能不能调用 swap(语法合法),而 std::is_nothrow_swappable_v<t></t> 多了一层语义约束:要求这个 swap 必须是 noexcept 的。两者不互为子集,也不等价。

性能 / 兼容性影响:在 C++17 中二者都是标准 trait;C++14 没有 std::is_nothrow_swappable,需手动模拟(用 noexcept(swap(std::declval<t>(), std::declval<t>()))</t></t>)。

  • 泛型库中常同时检查:if constexpr (std::is_nothrow_swappable_v<t>) { /* fast path */ } else if constexpr (std::is_swappable_v<t>) { /* fallback */ }</t></t>
  • 别用 try/catch 围绕 swap 来“验证”它是否真的不抛——这既破坏语义,又掩盖了 trait 设计本意:它是给编译器看的契约,不是运行时守卫
  • 第三方类型(如 boost::optional)是否满足,得查它的文档或源码;不能假设“只要能 swap 就 nothrow”

最易被忽略的一点:这个 trait 的结果高度依赖你是否提供了正确的 swap 声明,以及是否把 noexcept 推导写完整。它不“智能”,只机械地查符号和属性——写错一个 noexcept,整个链就断了。

text=ZqhQzanResources