Effective C++ 条款25:考虑写出一个不抛异常的swap函数
Effective C++ 条款25:考虑写出一个不抛异常的swap函数
核心思想:为自定义类型提供高效的、不抛异常的swap特化实现,优先通过成员swap实现,再通过非成员函数调用,最后全特化std::swap,确保类型在泛型代码中高效交换。
⚠️ 1. 默认swap的性能陷阱
深拷贝性能问题:
class ResourceIntensive {
public:ResourceIntensive(const ResourceIntensive& other): data_(new int[other.size_]), size_(other.size_) {std::copy(other.data_, other.data_ + size_, data_);}// 默认swap通过拷贝构造实现 → 深拷贝
private:int* data_;size_t size_;
};// 交换成本:O(n)拷贝
std::swap(obj1, obj2); // 调用3次拷贝构造/赋值
异常安全问题:
class Connection {
public:~Connection() { release(); }Connection(const Connection&) = delete; // 不可拷贝// 默认swap无法工作
private:void release(); // 释放资源Handle handle_; // 系统资源句柄
};
🚨 2. 解决方案:定制swap实现
三步定制法:
// 步骤1:类内提供高效swap成员函数
class Widget {
public:void swap(Widget& other) noexcept {using std::swap; // 为内置类型准备swap(dataPtr, other.dataPtr); // 仅交换指针swap(size, other.size); // 交换辅助数据}private:Data* dataPtr; // 指向大型数据size_t size;
};// 步骤2:非成员swap函数调用成员swap
void swap(Widget& a, Widget& b) noexcept {a.swap(b); // 调用高效成员函数
}// 步骤3:全特化std::swap
namespace std {template<> void swap<Widget>(Widget& a, Widget& b) noexcept {a.swap(b);}
}
现代C++改进:
// C++11后推荐仅提供非成员swap(避免特化std)
namespace WidgetStuff {class Widget { /* ... 成员swap ... */ };void swap(Widget& a, Widget& b) noexcept {a.swap(b);}
}// 通用调用方式
template<typename T>
void process(T& a, T& b) {using std::swap; // 引入std::swapswap(a, b); // 通过ADL查找最佳swap
}
⚖️ 3. 关键原则与实现策略
实现方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
成员swap+非成员swap | 自定义命名空间类型 | 标准兼容,ADL支持 | 需额外命名空间 |
全特化std::swap | 标准库类型扩展 | 被std算法直接使用 | 禁止添加新模板到std |
偏特化std::swap | 类模板(C++98/03) | 支持泛型 | C++11起非法 |
移动语义swap | C++11支持移动的类型 | 自动高效(默认已优化) | 需类型支持移动操作 |
Pimpl惯用法优化:
// 前置声明
class WidgetImpl;class Widget {
public:void swap(Widget& other) noexcept {std::swap(pImpl, other.pImpl); // 仅交换指针}private:WidgetImpl* pImpl; // 指向实现类
};// 非成员swap
void swap(Widget& a, Widget& b) noexcept { a.swap(b); }
类模板swap实现:
// 类模板需在自身命名空间提供swap
namespace Custom {template<typename T>class Array { public:void swap(Array& other) noexcept {std::swap(data_, other.data_);std::swap(size_, other.size_);}private:T* data_;size_t size_;};// 非成员swap模板template<typename T>void swap(Array<T>& a, Array<T>& b) noexcept {a.swap(b);}
}// 使用示例
Custom::Array<int> arr1, arr2;
using std::swap;
swap(arr1, arr2); // 调用Custom::swap
💡 关键原则总结
- 高效交换原则
- 资源管理类必须提供高效swap
- 仅交换指针/句柄,而非完整数据
- 异常安全保证
- swap函数必须声明
noexcept
- 禁止在swap中抛出异常
- swap函数必须声明
- 定制实现三步曲
- 成员
swap
:执行实际交换操作 - 非成员
swap
:调用成员函数 - (可选) 全特化
std::swap
- 成员
- 泛型编程兼容
- 使用
using std::swap;
+ 无限定swap
调用 - 依赖ADL查找最佳swap实现
- 使用
危险使用重现:
class Bitmap { /* 大数据 */ }; class Widget { private:Bitmap* pb; // 指向大型数据 };// 使用默认swap Widget w1, w2; std::swap(w1, w2); // 深拷贝三次Bitmap数据 → 性能灾难
安全优化方案:
class Widget { public:void swap(Widget& other) noexcept {std::swap(pb, other.pb); // 仅交换指针} private:Bitmap* pb; };// 非成员swap(同一命名空间) void swap(Widget& a, Widget& b) noexcept {a.swap(b); }// 使用示例 Widget w1, w2;// 方式1:直接调用(明确类型) swap(w1, w2); // 方式2:泛型代码调用 template<typename T> void processWidgets(T& a, T& b) {using std::swap; // 必要声明swap(a, b); // 自动选择最佳swap }// 方式3:STL算法使用 std::vector<Widget> widgets; std::swap(widgets[0], widgets[1]); // 调用高效swap