std::exchange提升异常安全性因其将取旧值与设新值合并为原子操作,避免手动两步序列中赋值失败导致原始状态不可恢复;它等价于先move再赋值,中间无用户可控状态,适用于移动/拷贝类型,仅当新值构造或赋值可能抛异常时价值凸显。

std::exchange 为什么能提升异常安全性?
因为 std::exchange 把“取旧值”和“设新值”合并成一个原子操作,避免了手动写 old = x; x = new_val; 这种两步序列——万一 x = new_val 抛异常,old 已经被取走,原始状态不可恢复。
它底层等价于:auto old = std::move(x); x = std::forward<t>(new_val); return old;</t>,关键在 std::move(x) 和赋值之间没有用户可控的中间状态。
- 适用于所有支持移动或拷贝的类型,但只有当新值构造/赋值可能抛异常时,它的安全价值才真正体现
- 如果
T的移动构造/赋值是noexcept(比如int、std::unique_ptr),那std::exchange本身也基本不会抛异常 - 注意:它不保证整个函数异常安全,只保证这一步“读旧+写新”的完整性
用在 swap 或资源替换场景时怎么写才不出错?
典型误用是把它当普通赋值替代品,忽略返回值或类型匹配问题。比如交换两个成员,不能只写 std::exchange(a, b) 就完事——它返回的是 a 的旧值,不是 b 的旧值。
正确做法是配合临时变量或结构化绑定:
立即学习“C++免费学习笔记(深入)”;
auto old_a = std::exchange(a, b); // a 变成 b,old_a 是原来的 a b = std::move(old_a); // b 变成原来的 a
- 若
a和b类型不同(如std::string和const char*),std::exchange(a, "hello")会隐式转换,但可能触发非预期的构造(比如抛std::bad_alloc),这时要确认右侧是否noexcept - 在 RAII 对象中替换内部指针时,必须确保新指针构造不抛异常,否则旧资源可能已丢失(
std::exchange(ptr, new T)中new T若抛异常,ptr已置为nullptr)
和直接赋值比,性能和 ABI 兼容性有啥实际影响?
绝大多数情况下没区别:std::exchange(x, v) 编译后和 { auto t = std::move(x); x = std::move(v); return t; } 几乎一致,现代编译器能很好优化。
- 唯一可测差异出现在
v是右值且类型有开销较大的移动构造时——std::exchange强制移动两次(一次取旧,一次设新),而手写可能省一次(比如先swap) - ABI 上完全无影响:它只是个内联函数模板,不改变对象布局或调用约定
- 不要在热循环里为“显得更安全”盲目替换所有赋值;只在明确需要捕获旧值 + 保证赋值原子性的地方用
容易被忽略的 const 与引用陷阱
std::exchange 要求第一个参数是非 const 的左值引用,传 const 对象或临时量会编译失败,错误信息往往指向模板推导而非语义问题。
常见报错:error: no matching function for call to 'exchange(const std::string&, std::string)'
- 不能对
const成员变量使用,除非该成员本身是mutable(极少见) - 不能用于
std::vector::at(i)这类返回临时量的表达式(std::exchange(v.at(0), x)错误:不是可修改的左值) - 如果目标是
std::optional<t></t>的值,得写std::exchange(opt.value(), new_val),而不是std::exchange(opt, std::nullopt)——后者交换的是整个 optional,语义完全不同









