(一)
缺省情况下swap动作可由标准程序库提供的swap算法完成:
namespace std { template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } }这个函数是异常安全性编程的核心,并且是用来处理自我赋值可能性的一个常见机制
但是对某些类型而言,这些复制动作无一必要:其中主要的就是“以指针指向一个对象,内含真正数据”那种类型。多为“pimpl手法”(pointer to implementation的缩写)
<span style="color:#333333;"><span style="font-family:Verdana, Arial, Helvetica, sans-serif;font-size:12px;">class WidgetImpl { private: int a, b, c; std::vector<double> v; }; class Widget { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { *pImpl = *(rhs.pImpl); } private: WidgetImpl* pImpl; };</span></span>要置换两个Widget对象值,唯一要做的就是置换pImpl指针,缺省的swap算法不知道这一点。不只复制3个Widget还复制3个WidgetImpl对象。非常缺乏效率!
解决办法:
我们可以令Widget声明一个swap的public成员函数做真正的替换工作,然后将std::swap特化,令他调用该成员函数:
<span style="color:#333333;"><span style="font-family:Verdana, Arial, Helvetica, sans-serif;">class Widget { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { *pImpl = *(rhs.pImpl); } void swap(Widget& other) { using std::swap; swap(pImpl, other.pImpl); } private: WidgetImpl* pImpl; }; </span></span><pre name="code" class="cpp" style="line-height: 19px; text-align: justify;">class WidgetImpl { private: int a, b, c; std::vector<double> v; };namespace std{ template<> void swap<Widget>(Widget& a, Widget& b) { a.swap(b); } }
这种做法不只能够通过编译,还与STL容器有一致性,因为所有STL容器也都提供有public swap成员函数和std::swap特化版本(用以调用前者)。
(二)
假设Widget和WidgetImpl都是class templates而非classes:
<span style="color:#333333;"><span style="font-family:Verdana, Arial, Helvetica, sans-serif;">template <typename T> class WidgetImpl{…}; template <typename T> class Widget{…};</span></span>所谓的“partially specialize“:C++允许对类模板进行”部分特化“,但不允许对模板函数进行”部分特化“。下面的定义就是错误的(将上例中的Widget类写成模板类):
namespace std{ template<typename T> void swap<Widget<T>>(Widget<T>& a, Widget<T>& b){ //错误!不合法! a.swap(b); } }即使添加重载版本也行不通,因为标准命名空间是一个特殊的命名空间,客户可以全特化里面的模板,但是不能添加新的模板(包括类模板和函数模板)到标准命名空间中去。所以下面这种方法不行!
namespace std{ template<typename T> void swap(Widget<T>& a, Widget<T>& b){ //试图重载,不合法! a.swap(b); } }一般而言,重载function template没有问题,但std是个特殊的命名空间,管理也就比较特殊。客户可以全特化std内的templates,但不可以添加新的templates(或class或function或任何其他东西)到std里头。其实跨越红线的程序几乎仍可编译执行,但他们行为没有明确定义。所以不要添加任何新东西到std里头。
解决方法:
为了提供较高效的template特定版本。我们还是声明一个non-member
swap但不再是std::swap的特化版本或重载版本:
namespace WidgetStuff { template<typename T> class Widget{...}; template<typename T> void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); } }现在,任何地点的任何代码如果打算置换两个Widget对象,因而调用swap,C++的名称查找法则就是所谓“argument-dependent lookup”会找到WidgetStuff内的专属版本。
(三)
template<typename T> void doSomething(T& obj1, T& obj2) { swap(obj1, obj2); }上面的应该使用哪个swap?是std既有的那个一般化版本还是某个可能存在的特化版本等?你希望应该是调用T专属版本,并在该版本不存在的情况下调用std内的一般化版本,下面是你希望发生的事:
template<typename T> void doSomething(T& obj1, T& obj2) { using std::swap; //令std::swap在此函数内可用 swap(obj1, obj2); }c++的名称查找法则(name lookup rules)确保将找到global作用域或T所在之命名空间内的任何T专属的swap。如果T是Widget并且位于命名空间WidgetStuff内,编译器会找出WidgetStuff内的swap。如果没有T专属的swap存在,编译器就是用std内的swap,然而即便如此,编译器还是比较喜欢std::swap的T专属特化版本,而非一般化的那个template。
总结:
(1)如果swap的缺省实现码对你的class或class template提供可接受的效率,那么我们不需要额外做任何事。
(2)如果swap缺省实现版效率不足(某种pimpl),那么我们就像这样做:
1.提供一个public swap成员函数,这个函数绝不该抛出异常。
2.在class或template所在的命名空间内提供一个non-member swap, 并令他调用上述swap成员函数。
3.如果正编写一个class(而非class template),为你的class特化std::swap。并令他调用swap成员函数。
如果调用swap,确保包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸调用swap。
swap的一个最好的应用是帮助classes(class templates)提供强烈的异常安全性保障。(条款29对此提供了所有细节)此技术基于一个假设:成员版的swap绝不抛出异常。当你写一个自定版本的swap,提供的不只是高效置换对象值的办法,而且不抛出异常。一般,这两个swap特性是连在一起的,因为高效的swaps几乎总是基于对内置类型的操作(例如pimpl手法的底层指针),而内置类型上的操作绝不会抛出异常。请记住:
(1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
(2)如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),也请特化std::swap。
(3)调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何"命名空间资格修饰“。
(4)为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内加入某些std而言全新的东西。