copy-and-swap能解决赋值异常安全问题,因其将构造新状态与替换旧状态分离:先在临时对象中完成可能失败的操作,再以noexcept swap原子切换,确保原对象要么全更新、要么完全不变。

为什么 copy-and-swap 能解决赋值异常安全问题
因为默认的赋值运算符在中途抛异常时,可能留下“半更新”对象:原对象被部分修改、资源泄漏、或处于不一致状态。而 copy-and-swap 把“构造新状态”和“替换旧状态”彻底分离——先在临时对象里安全完成所有可能失败的操作(如内存分配、拷贝),成功后再用无异常的 swap 原子切换,原对象要么全换、要么完全不变。
关键前提是:你的 swap 函数必须是 noexcept 的,且不能抛异常;拷贝构造函数可以抛异常,但那只会让临时对象构造失败,不影响原对象。
operator= 实现中哪些地方最容易写错
常见错误不是逻辑错,而是破坏了异常安全契约:
- 漏掉自赋值检查?其实
copy-and-swap本身天然支持自赋值(swap(x, x)是合法且无害的),所以不用特判——加了反而多余 - 把
swap写成成员函数但没声明为noexcept,导致编译器不敢优化、甚至触发未定义行为(比如在std::vector重分配时调用它) - 在
swap里手动交换裸指针却忘了置空原指针,造成二次释放;或者交换std::unique_ptr时用了.release()而不是直接赋值 - 拷贝构造函数里做了深拷贝但没处理分配失败(比如没用
new (std::nothrow)或没捕获std::bad_alloc),导致异常从构造函数逃逸——这会让copy-and-swap的第一步就崩,但至少不伤原对象
什么时候不该用 copy-and-swap
它不是银弹。性能敏感路径或资源极重的对象要谨慎:
立即学习“C++免费学习笔记(深入)”;
- 每次赋值都触发一次完整拷贝 + 一次
swap,对大对象(如含几 MB 缓冲区的类)开销明显;若确定赋值频繁且异常极少,手写“强异常安全”的赋值(先释放再拷贝再重建)可能更优 - 类没有合适的
swap实现,或swap本身依赖外部锁/系统调用(无法保证noexcept) - 移动语义已足够——c++11 后,优先实现
operator=(T&&)移动赋值,它比copy-and-swap更轻量;而copy-and-swap主要保底应对左值赋值
示例中常见的误用:operator=(const T& other) { T temp(other); swap(*this, temp); } —— 这里 swap 必须是非成员函数(ADL 可见),且最好用 using std::swap; + swap(...) 调用,避免意外调用到不安全的默认版本。
如何验证你的 operator= 真的异常安全
不能只靠“没崩过”;得主动注入故障点:
- 在拷贝构造函数里某次 new 后手动
throw std::bad_alloc(),观察原对象是否保持原样(可用断言检查内部指针、计数器、文件描述符等) - 用
std::set_new_handler模拟内存耗尽,或用__gnu_cxx::throw_allocator(GCC)测试容器内部分配失败 - 检查编译器警告:Clang/GCC 对未标记
noexcept的swap在异常上下文中会提示 “call to function ‘swap’ that is potentially throwing” - 静态分析工具如
clang++ -fsanitize=undefined或AddressSanitizer能抓到 swap 后原对象被误用的问题
最常被忽略的一点:异常安全 ≠ 不抛异常,而是“失败时不留垃圾”。哪怕你所有函数都加了 noexcept,只要 swap 里有未检查的 close(fd) 失败,就可能让文件句柄泄露——这种错误不会 crash,但会在长期运行中悄悄拖垮系统。