当前位置: 首页 > 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.jsqmd.com/news/115968/

相关文章:

  • Item25--考虑写出一个不抛异常的 swap 函数
  • 3562. 折扣价交易股票的最大利润
  • 【2025终极测评】10款常见降AI率工具大汇总(含0元免费降AI版本)
  • 算法日记专题:位运算I(汉明距离I II 面试题:判断是不是唯一的字符 丢失的数字 两个整数相加)
  • Item21--必须返回对象时,别妄想返回其 reference
  • Item15--在资源管理类中提供对原始资源的访问
  • 1985-2024年中国绿色专利数据库(绿色技术专利分类)
  • Item22--将成员变量声明为 private
  • Item16--`new` 与 `delete` 的对应规则
  • 3777. 使子字符串变交替的最少删除次数
  • item11--在 operator= 中处理“自我赋值
  • 预见2026:家居新品首秀平台选择战略——五大核心展会深度评估与推荐 - 匠子网络
  • Item20--宁以 pass-by-reference-to-const 替换 pass-by-value
  • 研究生必备!8个免费AI论文工具,半天生成5000字问卷论文还有高信度数据
  • Item20--宁以 pass-by-reference-to-const 替换 pass-by-value
  • Item17--以独立语句将 `new` 到的对象置入智能指针
  • Item17--以独立语句将 `new` 到的对象置入智能指针
  • 3433. 统计用户被提及情况
  • Item19--设计 class 犹如设计 type
  • 国外软件,安装即时专业版!
  • Item19--设计 class 犹如设计 type
  • basic_regex
  • c++狼人杀
  • 宠物识别丨基于弱监督学习的宠物视频内容自动标注技术实践 - 指南
  • 朴易天下:道家修行的专业术语分享
  • 个人投资者的落地路径:从“说人话,做量化”到实盘前的三道关
  • 神经网络中的 block 和 module
  • item13--使用对象管理资源
  • 深入解析:蓝桥杯基础算法精讲:模拟与高精度运算实战指南
  • item12-- 拷贝一个对象的所有组成部分