1. swap如此重要
Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11)。Swap非常有用,恰当的实现swap是非常重要的,与重要性伴随而来的是一些并发症。在这个条款中,我们将探索这些并发症以及如何处理它们。
2. swap的傻瓜实现方式及缺陷
2.1 swap函数的默认实现
Swap函数就是将两个对象的值进行交换,可以通过使用标准的swap算法来实现:
1 namespace std {
2
3 template<typename T> // typical implementation of std::swap;
4
5 void swap(T& a, T& b) // swaps a’s and b’s values
6
7 {
8
9 T temp(a);
10
11 a = b;
12
13 b = temp;
14
15 }
16
17 }
只要你的类型支持拷贝(拷贝构造函数和拷贝赋值运算符),默认的swap实现不需要你做一些特别的工作来支持它。
2.2 swap函数默认实现的缺陷——有可能效率低
然而,默认的swap实现也许并没有让你激动,它包括三次拷贝:a 拷贝到temp,b拷贝到a, temp拷贝到b。对于一些类型来说,这些拷贝不是必须的,默认的swap将你从快车道拉到了慢车道。
这些不需要拷贝的类型内部通常包含了指针,指针指向包含真实数据的其他类型。使用这种设计方法的一个普通的例子就是“pimpl idiom”(指向实现的指针 Item 31).举个例子:
1 class WidgetImpl { // class for Widget data;
2
3 public: // details are unimportant
4
5 ...
6
7 private:
8
9 int a, b, c; // possibly lots of data —
10
11 std::vector<double> v; // expensive to copy!
12
13 ...
14
15 };
16
17 class Widget { // class using the pimpl idiom
18
19 public:
20
21 Widget(const Widget& rhs);
22
23 Widget& operator=(const Widget& rhs) // to copy a Widget, copy its
24
25 { // WidgetImpl object. For
26
27 ... // details on implementing
28
29 *pImpl = *(rhs.pImpl); // operator= in general,
30
31 ... // see Items 10, Item 11, and Item 12.
32
33 }
34
35 ...
36
37 private:
38
39 WidgetImpl *pImpl; // ptr to object with this
40
41 }; // Widget’s data
为了交换两个Widget对象的值,我们实际上唯一需要做的是交换两个pImpl指针,但是默认的swap算法没有办法能够获知这些。它不仅拷贝了三个Widget对象,还拷贝了三个WidgetImpl对象。非常没有效率,也不令人鸡冻。
3. 如何实现一个高效的swap
3.1 为普通类定义全特化版本swap
我们需要做的就是告诉std::swap当Widget对象被swap的时候,执行swap的方式是swap内部的pImpl指针。也就是为Widget定制一个std::swap。这是最基本的想法,看下面的代码,但是不能通过编译。。
1 namespace std {
2
3 template<> // this is a specialized version
4
5 void swap<Widget>(Widget& a, // of std::swap for when T is
6
7 Widget& b) // Widget
8
9 {
10
11 swap(a.pImpl, b.pImpl); // to swap Widgets, swap their
12
13 } // pImpl pointers; this won’t
14
15 compile
16
17 }
开始的”templpate<>”说明这是对std::swap的模板全特化(total template specializaiton),名字后面的”<Widget>”是说明这个特化只针对T为Widget类型。换句话说,当泛化的swap模板被应用到Widget类型时,应该使用上面的实现方法。一般来说,我们不允许修改std命名空间的内容,但是却允许使用我们自己创建的类型对标准模板进行全特化。
但是这个函数不能编译通过。这是因为它尝试访问a和b中的pImpl指针,它们是private的。我们可以将我们的特化函数声明成friend,但是传统做法却是这样:在Widget中声明一个真正执行swap的public成员函数swap,让std::swap调用成员函数:
1 class Widget { // same as above, except for the
2
3 public: // addition of the swap mem func
4
5 ...
6
7 void swap(Widget& other)
8
9 {
10
11 using std::swap; // the need for this declaration
12
13 // is explained later in this Item
14
15 swap(pImpl, other.pImpl); // to swap Widgets, swap their
16
17 } // pImpl pointers
18
19 ...
20
21 };
22
23 namespace std {
24
25 template<> // revised specialization of
26
27 void swap<Widget>(Widget& a, // std::swap
28
29 Widget& b)
30
31 {
32
33 a.swap(b); // to swap Widgets, call their
34
35 } // swap member function
36
37 }
这种做法不仅编译能通过,同STL容器一致,它们都同时为swap提供了public成员函数版本和调用成员函数的std::swap版本。
3.2 为模板类定义偏特化版本swap
然而假设Widget和WidgetImpl换成了类模版,我们就将存储在WidgetImpl中的数据类型替换成一个模板参数:
1 template<typename T>
2
3 class WidgetI