Item 25 不应抛出异常的swap

通用的swap可以在stl里找到:

 

namespace std { template<typename T> // 1. std::swap的实现 void swap(T& a, T& b) { T temp(a); a = b; // 2. 只要T支持拷贝 b = temp; } }

 

有很多类使用pimpl来分离实现与接口,对它们的swap要慎重。

 

class WidgetImpl { // 真正的数据与函数实现放在这个类里 public: ... private: int a, b, c; // 会有很多数据,都拷贝则代价很大 std::vector<double> v; ... }; class Widget { // 抽象出来的接口,使用pimpl引用具体实现 public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) // to copy a Widget, copy its { // WidgetImpl object. For ... // details on implementing *pImpl = *(rhs.pImpl); // operator= in general, ... // see Items 10, 11, and 12. } ... private: WidgetImpl *pImpl; };

 

其实,swap只要交换二者的指针即可,不用拷贝里面的数据:

 

void Widget::swap(Widget& other) { // 不要在该成员函数里抛出异常! using std::swap; swap(pImpl, other.pImpl); } namespace std { // 这样定义方式叫做:total template specialization template<> void swap<Widget>(Widget& a, Widget& b) { a.swap(b); } }

 

STL里的容器都是这样实现swap的。

现在假设Widget和WidgetImpl都是模板类,即:

 

template<typename T> class Widget { ... }; template<typename T> class WidgetImpl { ... };

 

那么,如何实现swap呢?下面这样的思路可以吗:

 

namespace std { template<typename T> void swap<Widget<T> >(Widget<T>& a, Widget<T>& b) // 不可以。编译错 { a.swap(b); } }

 

实际上,对模板函数的定义进行“partial specialization”在C++中是无效的。如果编译通过,说明编译器有问题。
不过,对模板类,可以进行“partial specialization”。

那么,重载swap可以解决问题吗:

 

namespace std { template<typename T> void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); } }

 

但是,std这个名字空间,是不适合用上面的方法解决问题的。这属于对std空间增加了新的模板类。
而std空间的内容,只有C++委员会才有权决定。
可能上面的代码在一些编译器上可以正常运行,比如VC,但实际上属于“未定义行为”!

需要稍稍改一下:

 

namespace WidgetStuff { ... // templatized WidgetImpl, etc. template<typename T> // as before, including the swap class Widget { ... }; // member function ... template<typename T> void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); } }

 

把swap移到新的名字空间了。那么,像下面的代码:

 

using WidgetStuff::Widget; Widget<int> w; Widget<int> w2; swap(w, w2);

 

C++将应用argument-dependent lookup (Koenig lookup)规则,进行查找,最后找到以Widget作为参数的swap函数。

从客户的角度看下面的代码:

 

template<typename T> void doSomething(T& obj1, T& obj2) { ... swap(obj1, obj2); ... }

 

哪一个swap会被调用到呢?有可能是std里的通用版本;有可能是std里完全特化的版本;有可能是在某个名字空间里的重载版本。
如果你想让它调用重载版本,当重载版本不存在,就调用std通用版本,那么可以像下面这样写:

 

template<typename T> void doSomething(T& obj1, T& obj2) { using std::swap; // 它会在std通用版本和重载版本里 swap(obj1, obj2); // 挑选最合适的函数 ... }

 

C++查找swap的规则是:先在全局名字空间中找,然后在T的名字空间中找。
在std中,C++优先使用特化的版本,如果该版本存在的话。

有些程序员喜欢这样写:

std::swap(obj1, obj2);

这样就限制了swap的查找。
为了帮助这样的代码也能提高效率,你要在你的类里,定义特化的版本。

基于对“异常安全”的考虑,swap成员函数不应该抛出异常。只有这样,swap才能做到异常安全。
swap的行为基于copy ctor和拷贝赋值。这两个函数可以抛出异常。
自定义swap是为了更高的效率,应该只处理内建类型,所以不应该抛出异常。

你可能感兴趣的:(c,Class,编译器)