当前位置: 首页 > news >正文

Item25--考虑写出一个不抛异常的 swap 函数

1. 核心现状:默认的 std::swap 够用吗?

绝大多数情况:够用(Rule of Zero)

如果你的类遵循 Rule of Zero(只包含标准库容器、智能指针等成员),你不需要手写 swap

class ModernWidget {std::string name;std::vector<int> data;// 编译器自动生成的 Move Constructor 和 Move Assignment 极其高效
};

对于这种类,标准库默认的 swap 会调用移动语义,其成本是 $O(1)$。手写没有任何收益。

需要手写的场景:Pimpl 惯用法与资源管理

当你使用了 Pimpl (Pointer to Implementation) 模式,或者手动管理原始指针时,默认的 move 行为虽然正确,但为了代码语义的清晰性异常安全性(noexcept)以及支持 Copy-and-Swap 惯用法,手动实现 swap 依然是最佳实践。


2. 标准实现“三步走”战略

如果你决定要手写 swap,请必须严格遵循以下三个步骤,以确保它能被所有系统(STL算法、第三方库)正确调用。

第一步:提供一个 public 的 swap 成员函数

这是真正干活的地方。它必须是高效的(通常是指针交换),且必须保证不抛出异常

class Widget {
public:// 加上 noexcept 是现代 C++ 的关键void swap(Widget& other) noexcept {using std::swap; // 针对成员逐一交换// 如果是 Pimpl,这里只交换一个指针,非常快swap(pImpl, other.pImpl); }
private:WidgetImpl* pImpl;
};

第二步:在类所在的同一命名空间,提供非成员 swap

这是为了支持 ADL (Argument-Dependent Lookup)。如果你的类是模板类,这是让外部调用 swap 的唯一方法(因为无法偏特化 std::swap)。

namespace WidgetStuff {class Widget { ... }; // 上面的类定义// 非成员函数,单纯转发给成员函数void swap(Widget& a, Widget& b) noexcept {a.swap(b);}
}

第三步:特化 std::swap (可选,但推荐)

如果你的类不是模板类,你应该特化 std::swap。这是为了照顾那些老旧的、或者写得不规范的代码(它们可能直接写了 std::swap(a, b) 而没有利用 ADL)。

namespace std {template<>void swap<WidgetStuff::Widget>(WidgetStuff::Widget& a, WidgetStuff::Widget& b) noexcept {a.swap(b);}
}

3. 如何正确调用 swap?

这是 Item 25 中最容易被忽略,但最重要的部分。无论你是库的作者还是使用者,在代码中想要交换两个对象时,严禁直接写 std::swap

❌ 错误的调用:

std::swap(obj1, obj2); 
// 强行指定了 std 版本。
// 如果 obj1 是模板类,且只有 ADL 版本的 swap,这行代码会回退到默认的 std 实现(可能导致不必要的移动/拷贝)。

✅ 正确的调用(The "Using" Pattern):

void doSomething(Widget& obj1, Widget& obj2) {using std::swap;  // 1. 让 std::swap 进入可见范围swap(obj1, obj2); // 2. 调用 swap(不带 std:: 前缀)
}

查找逻辑(编译器如何思考):

  1. ADL 优先:编译器先看 Widget 所在的命名空间有没有 swap?(如果有,就用它)。
  2. 特化次之:如果没有 ADL 版本,编译器看 std::swap 有没有针对 Widget 的特化?(如果有,就用它)。
  3. 默认保底:如果都没有,就用 std::swap 的通用模板(移动语义)。

4. 现代 C++ 的核心补充:noexcept

在 Item 25 编写时,异常规范(Exception Specification)还很鸡肋。但在现代 C++ 中,noexcept 对性能至关重要。

  • 场景std::vector<Widget>::push_back 导致扩容。
  • 逻辑:vector 需要把旧内存的数据迁移到新内存。
    • 如果 Widget 的移动操作(或 swap)是 noexcept 的,vector 会移动元素(快)。
    • 如果不是 noexcept,vector 为了保证数据不丢失(Strong Exception Guarantee),会强制拷贝元素(慢)。
  • 结论手写 swap 时,务必加上 noexcept

总结清单 (The Checklist)

  1. Rule of Zero 优先:如果类成员都是可移动的(STL容器、智能指针),不要手写 swap,使用编译器生成的即可。
  2. Pimpl 模式必写:如果实现了 Pimpl,必须手写 swap 以确保浅拷贝语义和异常安全性。
  3. 遵循三步走
    • public member swap (with noexcept)
    • non-member swap (in same namespace)
    • std::swap specialization (if not a template class)
  4. 调用规范:永远使用 using std::swap; swap(a, b);
http://www.rkmt.cn/news/127921.html

相关文章:

  • 3562. 折扣价交易股票的最大利润
  • Item15--在资源管理类中提供对原始资源的访问
  • Item21--必须返回对象时,别妄想返回其 reference
  • 1985-2024年中国绿色专利数据库(绿色技术专利分类)
  • Item16--`new` 与 `delete` 的对应规则
  • 预见2026:家居新品首秀平台选择战略——五大核心展会深度评估与推荐 - 匠子网络
  • 国外软件,安装即时专业版!
  • 个人投资者的落地路径:从“说人话,做量化”到实盘前的三道关
  • item13--使用对象管理资源
  • sub_match
  • python django flask酒店客房管理系统数据可视化分析系统_gq8885n3--论文md5
  • python django flask鹿幸公司员工食堂在线点餐餐饮餐桌预约管理系统的设计与实现_utcnqqs0--论文
  • error_code
  • 好用的大型牛场水滴粉碎机技术强的
  • function bind
  • Item6--若不想使用编译器自动生成的函数,就该明确拒绝
  • 我发现LLM解析基因数据优化抗癌药剂量,患者副作用直降40%
  • 线程(1)
  • 日记12.16
  • 信息与关系:涌现的三大核心原则
  • 论文AIGC查重率高怎么办?6个降AI率工具和技巧,AI率从100%降到3%! - 还在做实验的师兄
  • 离散化遍历
  • 28
  • 人工智能与大数据:智能决策的新时代 - 教程
  • 花边服饰银发红眸者山间近景
  • 互联网大厂Java面试:从Spring Boot到微服务架构的技术探讨
  • 性价比高的老房换新实用门窗品牌精选指南排名
  • 12月20日总结 - 作业----
  • 老牌软件,输入序列号可激活商业版!
  • 熬夜刷手机不愿睡觉,这是一种心理问题吗?