news 2026/6/12 15:27:51

Effective C++ 条款25:考虑写一个不抛异常的 swap 函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Effective C++ 条款25:考虑写一个不抛异常的 swap 函数

Effective C++ 条款25:考虑写一个不抛异常的 swap 函数

当 std::swap 对你的类型效率不高时,提供一个 swap 成员函数,并确定这个函数不抛出异常。如果你提供一个 member swap,也该提供一个 non-member swap 用来调用前者。对于 classes(而非 templates),要特别特化 std::swap。

一、引言:swap 的重要性被低估了

swap是 C++ 中最简单却最重要的函数之一。它不仅是 STL 排序算法的基石,更是**异常安全编程(Exception-Safe Programming)**的核心工具。

但很多人不知道:默认的std::swap可能对你的类效率极低,而自定义一个不抛异常swap可以带来质的飞跃。


二、std::swap 的默认实现及其问题

2.1 默认实现

namespacestd{template<typenameT>voidswap(T&a,T&b){Ttemp(a);// 调用拷贝构造a=b;// 调用拷贝赋值b=temp;// 调用拷贝赋值}}

2.2 问题场景:Pimpl 惯用法

假设你有一个使用 Pimpl(Pointer to Implementation)的 Widget 类:

classWidgetImpl{public:// 大量数据成员inta,b,c;std::vector<double>data;std::map<std::string,std::string>metadata;// ... 可能成百上千个成员};classWidget{public:Widget(constWidget&rhs);Widget&operator=(constWidget&rhs){*pImpl=*rhs.pImpl;// 深拷贝所有数据!return*this;}~Widget();private:WidgetImpl*pImpl;// 指向实现的指针};

使用默认std::swap

Widget w1,w2;std::swap(w1,w2);// ❌ 三次深拷贝!性能灾难!

实际上,我们只需要交换两个指针即可!


三、自定义 swap:三步走策略

3.1 第一步:提供成员 swap

classWidget{public:// ... 其他成员函数// ✅ 成员 swap——高效且不抛异常voidswap(Widget&other)noexcept{usingstd::swap;// 允许 ADLswap(pImpl,other.pImpl);// 只交换指针!}private:WidgetImpl*pImpl;};

3.2 第二步:提供 non-member swap

// 在 Widget 的命名空间中namespaceWidgetStuff{classWidget{/* ... */};// ✅ non-member swap——调用成员 swapvoidswap(Widget&a,Widget&b)noexcept{a.swap(b);}}

3.3 第三步:特化 std::swap(仅对类,不对类模板)

// ✅ 对具体类特化 std::swapnamespacestd{template<>voidswap<Widget>(Widget&a,Widget&b)noexcept{a.swap(b);}}

⚠️重要限制:C++ 标准不允许对函数模板进行偏特化,也不允许在std命名空间中添加新的模板。因此,对于类模板,我们只能使用 non-member swap 的方式,不能特化std::swap


四、类模板的 swap 处理

4.1 类模板的情况

namespaceWidgetStuff{// 类模板template<typenameT>classWidgetImpl{// ...};template<typenameT>classWidget{public:voidswap(Widget&other)noexcept{usingstd::swap;swap(pImpl,other.pImpl);}private:WidgetImpl<T>*pImpl;};// ✅ non-member swap 模板——这是推荐做法template<typenameT>voidswap(Widget<T>&a,Widget<T>&b)noexcept{a.swap(b);}}

4.2 为什么不能偏特化 std::swap?

// ❌ 错误:C++ 不允许函数模板偏特化namespacestd{template<typenameT>voidswap<Widget<T>>(Widget<T>&a,Widget<T>&b){// 编译错误!a.swap(b);}}// ❌ 错误:不允许在 std 中添加新模板namespacestd{template<typenameT>voidswap(Widget<T>&a,Widget<T>&b){// 未定义行为!a.swap(b);}}

五、正确使用 swap 的惯用法

5.1 使用 using std::swap

template<typenameT>voiddoSomething(T&obj1,T&obj2){usingstd::swap;// 引入 std::swapswap(obj1,obj2);// 不加任何命名空间限定!}

为什么这样写?

C++ 的名称查找规则(ADL + 常规查找)会按以下优先级选择:

  1. T 的专属 non-member swap(通过 ADL 找到)
  2. std::swap 的特化版本
  3. std::swap 的通用模板
Widget w1,w2;doSomething(w1,w2);// 调用 WidgetStuff::swap(通过 ADL)inta=1,b=2;doSomething(a,b);// 调用 std::swap(内置类型无专属 swap)

5.2 异常安全:swap 的强保证

swap不抛异常是实现**强异常安全(Strong Exception Safety)**的关键:

classWidget{public:Widget&operator=(constWidget&rhs){// 基本保证:如果异常发生,对象可能处于中间状态// *pImpl = *rhs.pImpl; // 可能抛异常!// ✅ 强保证:copy-and-swap 惯用法Widgettemp(rhs);// 拷贝可能抛异常,但原对象未改变swap(temp);// swap 不抛异常,安全交换return*this;// temp 在作用域结束时销毁,释放原资源}voidswap(Widget&other)noexcept{usingstd::swap;swap(pImpl,other.pImpl);}private:WidgetImpl*pImpl;};

copy-and-swap 的优势

特性直接赋值copy-and-swap
异常安全基本保证强保证
自我赋值需要检查自动安全
代码复杂度中等简单优雅

六、实际应用场景

6.1 资源管理类

classResourceManager{public:ResourceManager():resource_(acquireResource()){}~ResourceManager(){releaseResource(resource_);}// 移动操作ResourceManager(ResourceManager&&other)noexcept:resource_(other.resource_){other.resource_=nullptr;}ResourceManager&operator=(ResourceManager&&other)noexcept{if(this!=&other){ResourceManagertemp(std::move(other));swap(temp);}return*this;}// ✅ 不抛异常的 swapvoidswap(ResourceManager&other)noexcept{usingstd::swap;swap(resource_,other.resource_);}private:Resource*resource_;Resource*acquireResource();voidreleaseResource(Resource*r);};// non-member swapvoidswap(ResourceManager&a,ResourceManager&b)noexcept{a.swap(b);}

6.2 自定义容器

template<typenameT>classMyVector{public:MyVector():data_(nullptr),size_(0),capacity_(0){}~MyVector(){delete[]data_;}// 移动构造MyVector(MyVector&&other)noexcept:data_(other.data_),size_(other.size_),capacity_(other.capacity_){other.data_=nullptr;other.size_=other.capacity_=0;}// ✅ 不抛异常的 swap——交换三个指针即可voidswap(MyVector&other)noexcept{usingstd::swap;swap(data_,other.data_);swap(size_,other.size_);swap(capacity_,other.capacity_);}// copy-and-swap 赋值MyVector&operator=(MyVector other){// 按值传递,触发拷贝swap(other);// 与临时对象交换return*this;}private:T*data_;size_t size_;size_t capacity_;};// non-member swaptemplate<typenameT>voidswap(MyVector<T>&a,MyVector<T>&b)noexcept{a.swap(b);}

6.3 多成员类的 swap

classComplexWidget{public:voidswap(ComplexWidget&other)noexcept{usingstd::swap;// 逐一交换所有成员——全部是不抛异常的操作swap(name_,other.name_);// string 的 swap 不抛异常swap(dimensions_,other.dimensions_);// vector 的 swap 不抛异常swap(metadata_,other.metadata_);// map 的 swap 不抛异常swap(cache_,other.cache_);// unique_ptr 的 swap 不抛异常swap(isVisible_,other.isVisible_);// bool 交换不抛异常}private:std::string name_;std::vector<int>dimensions_;std::map<std::string,std::string>metadata_;std::unique_ptr<Cache>cache_;boolisVisible_;};

七、为什么 swap 必须不抛异常?

7.1 异常安全等级

等级描述swap 的作用
基本保证异常发生后,对象处于有效但不确定状态
强保证异常发生后,对象状态回滚到操作前核心工具
不抛保证操作绝不抛异常swap 本身应达到

7.2 swap 不抛异常的原因

voidswap(Widget&a,Widget&b)noexcept{// 只交换指针/内置类型// 这些操作在硬件层面是原子的,不可能失败WidgetImpl*temp=a.pImpl;a.pImpl=b.pImpl;b.pImpl=temp;}

swap 通常只涉及:

  • 指针交换
  • 内置类型交换
  • 标准库类型的swap(已保证不抛异常)

这些操作不可能分配内存,因此不可能抛std::bad_alloc,也没有其他失败条件。


八、现代 C++ 的简化:Rule of Zero

在 C++11 及以后,如果你的类只包含标准库容器、智能指针等自动管理资源类型,遵循 Rule of Zero

// ✅ Rule of Zero:不需要自定义 swapclassModernWidget{public:// 编译器生成的默认构造、析构、移动、拷贝都正确// std::swap 对这些成员的组合也能高效工作private:std::string name_;std::vector<double>data_;std::unique_ptr<Impl>pImpl_;std::shared_ptr<Cache>cache_;};// 直接使用 std::swap 即可ModernWidget w1,w2;std::swap(w1,w2);// ✅ 高效,因为所有成员都有高效的 swap

只有当类包含原始指针需要手动管理的资源时,才需要自定义 swap。


九、总结

核心原则

  1. 默认 std::swap 可能效率低下——对于 Pimpl 类尤其如此
  2. 自定义 swap 应该只交换指针/句柄——避免深拷贝
  3. swap 必须声明为 noexcept——它是异常安全编程的基石
  4. 提供成员 swap + non-member swap + std::swap 特化——完整的 swap 支持

实现检查清单

classMyClass{public:// 1. 成员 swap,noexceptvoidswap(MyClass&other)noexcept{usingstd::swap;swap(pImpl_,other.pImpl_);}// 2. copy-and-swap 赋值(可选但推荐)MyClass&operator=(MyClass other){swap(other);return*this;}private:Impl*pImpl_;};// 3. non-member swapvoidswap(MyClass&a,MyClass&b)noexcept{a.swap(b);}// 4. std::swap 特化(仅对非模板类)namespacestd{template<>voidswap<MyClass>(MyClass&a,MyClass&b)noexcept{a.swap(b);}}

使用 swap 的正确姿势

template<typenameT>voidgenericFunction(T&a,T&b){usingstd::swap;// 引入标准 swapswap(a,b);// 让编译器选择最佳版本}

📌记住:swap 是异常安全编程的瑞士军刀。一个正确实现的 noexcept swap 不仅能让你的类性能飞跃,更能为所有使用它的代码提供强异常安全保证。这是专业 C++ 开发者必须掌握的技术。


参考与延伸阅读

  • 《Effective C++》第三版,Scott Meyers,条款25
  • 《Exceptional C++》,Herb Sutter,关于异常安全的深入讨论
  • CppReference: noexcept specifier
  • CppReference: Copy and swap idiom

如果这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、留言 💬!你的支持是我持续输出的动力!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 15:24:59

三步开启AI象棋助手:让普通玩家也能享受大师级分析体验

三步开启AI象棋助手&#xff1a;让普通玩家也能享受大师级分析体验 【免费下载链接】VinXiangQi Xiangqi syncing tool based on Yolov5 / 基于Yolov5的中国象棋连线工具 项目地址: https://gitcode.com/gh_mirrors/vi/VinXiangQi 你是否曾经在下象棋时感到迷茫&#xf…

作者头像 李华
网站建设 2026/6/12 15:22:05

AI搜索优化:让品牌出现在AI的答案里

在人工智能快速更新迭代的当今时代, 用户获取信息的方式正处于发生根本性转变的状态。越来越多的人不再借助传统搜索引擎逐一条目地翻阅网页, 而是直接朝着AI助手提出问题, 诸如“哪个品牌的空调具备最节能的特性”“2026年值得进行投资的新能源股票都存有哪些”。这样的一种趋…

作者头像 李华
网站建设 2026/6/12 15:13:51

vRealize Operations Manager 巡检报告自动化配置实战

1. 为什么需要自动化巡检报告 作为虚拟化管理员&#xff0c;我每天最头疼的事情之一就是手动生成各种巡检报告。记得有一次要给5个客户环境做月度健康检查&#xff0c;光是导出报告、修改标题、发送邮件就花了大半天时间&#xff0c;还差点发错客户。这种重复性工作不仅耗时耗力…

作者头像 李华
网站建设 2026/6/12 15:12:39

D2DX:为《暗黑破坏神2》注入现代图形引擎的深度重构方案

D2DX&#xff1a;为《暗黑破坏神2》注入现代图形引擎的深度重构方案 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 在4K显示…

作者头像 李华