-
swap 是一个有趣的函数。原本它只是STL的一部分,后面成为异常安全性编程的重要支柱(见条款29),以及处理自我赋值的常见机制(见条款11)。swap如此有用,考虑其合理的实现变得十分重要。
-
所谓 swap(置换),就是将两个对象的值彼此赋值给对方。
-
默认情况下,swap 可以交给 标准程序库提供的 swap 算法完成,其典型实现正如预期。
namespace std{ templatevoid swap(T& A,T& B) noexcept{ T Temp(A); B=A; A=Temp; } } 只要类型 T 支持 copying 函数,默认的 swap 函数就会为你置换对应的对象。
但默认实现的 swap 十分平淡,它只是简单的复制了对象的值,而未对特殊情况做处理。举个例子,对于以指针指向一个对象,内含真正的数据的那种类型,也就是我们所说的 pimpl 手法(pointer to implemention),一旦要交换这种对象的值,默认的swap 会复制整个对象,但我们期望的是只需复制其中包含数据的指针。所以有必要编写自己的 swap 函数。
-
对于使用 pimpl 手法的对象,示例如下:
#include
class WidgetData { public: }; class Widget { public: Widget(WidgetData* mData): Data(mData) {} public: void swap(Widget& Other) { std::swap(Data, Other.Data); } private: WidgetData* Data; }; namespace std { inline void swap(Widget& A, Widget& B) noexcept { //尝试全特化 std::swap失败,可以用重载代替。 A.swap(B); } } 我们可以在 Widget 类声明一个名为 swap 的 public 成员函数,然后同时在 std 命名空间提供特殊化的 swap 函数,令它调用该成员函数。
这种做法不仅可以通过编译,还与 STL 容器保持一致性,因为所有 STL 容器都提供 public swap 成员函数和 std::swap 特化版本。
-
注意,C++只允许对 class templates 偏特化,禁止在 function templates 偏特化。一个比较好的替换方式是重载。但是注意 std 是一个特殊的命名空间,其管理规则也比较特殊,客户可以全特化 std 内的 templates,但不可以添加新的 classes,functions 或者其他任何东西到 std 中。标准委员会禁止我们膨胀那些已经声明好的东西。所以如果你希望你的软件有可预期的行为,请不要添加任何新东西到std里头。
-
目前为止,我编写的任何东西都与 swap 有关,这里已经存在多个版本的 swap,从客户的角度来看,我怎么确保可以调用到我想要的那个版本的 swap函数呢?其实,一旦编译器看到对 swap 的调用,它便会查找适当的 swap 并调用之。 C++ 的名称查找法则(name lookup rules)会先在当前作用域寻找目标函数,找不到再扩大作用域。而使用类似 using xxx,可以提醒编译器使用该xxx命名空间中函数。注意,特化版本的函数会优先考虑,当然,也要注意二义性的问题。
-
此刻,我们已经完成对所有情况的讨论,来做个总结。
如果默认的 swap 实现有比较好的执行效率,那么就不需要做额外的事情。
如果默认的 swap 实现效率不足,那意味着你的class或者template做了类似pimpl的事情,请试着做下面的事情:- 提供一个 public swap 成员函数,让它高效的置换你类型的两个对象值。稍后我将解释,这个函数绝不该抛出任何异常。
- 在你的 classes 或 template 所在的命名空间内提供一个 non-member swap,并令它调用上述 swap 成员函数。
- 如果你正编写一个 class,而非 class template,为你的 class 特化 std::swap。并令它调用你的 swap 成员函数。
-
唯一还未明确的是我的劝告:成员版 swap 绝不可抛出异常。因为 swap 一个最好的应用就是帮助 classes ,templats 提供强烈的异常安全报障。条款29对此提供了所有的细节。注意,该劝告只施行于成员版,不施行于非成员版。因为 swap 默认版本是以拷贝函数为基础,而两者都允许抛出异常。
- 当 std::swap 对你的类型效率不高时,提供一个 swap 成员函数,并确定这个函数不抛出异常。
- 如果你提供一个 member template,也该提供一个 non-member swap 用来调用前者。对于 classes,而非 templates,也请特化 std::swap。
- 调用 swap 时应针对 std::swap 使用 using 声明式,然后调用 swap 并且不带任何命名空间资格修饰。
- 为用户定义类型进行 std templates 全特化是好的,但千万不要尝试在 std 内加入某些对 std 而言是全新的东西。



