1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下:
namespace std{ template<typename T> swap(T& lhs,T& rhs){ T temp(lhs); lhs=rhs; rhs=temp; } }
只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某些类,这种默认的转换方式代价太大,比如:
class Demo{ public: Demo(const Demo&); Demo& operator=(const Demo& rhs){ ... *ptr=*rhs.ptr; ... } ... private: vector<int>* ptr; }
如果按照标准库swap的默认行为所付出的的代价很大,而实际上只要置换两个指针的地址即可.
2. 解决1的一种方法是对标准库std命名空间内的swap函数模板进行特化,如下:
namespace std{ template<> void swap<Demo>(Demo& lhs,Demo& rhs){ swap(lhs.ptr,rhs.ptr); } }
通常我们不能改变标准库std命名空间内的东西,但是C++允许为标准templates制造特化版本,因此以上代码理论上来说是可行的,但由于Demo的ptr成员被设为private,所以以上代码通不过编译.我们可以为Demo声明一个名为swap的public成员函数执行相关操作,然后令标准库特化swap调用该函数,如下:
class Demo{ public: ... void swap(Demo& rhs){ using std::swap; swap(ptr,rhs.ptr); } ... } namespace std{ void swap(Demo& lhs,Demo& rhs){ lhs.swap(rhs); } }
这种做法不但可以通过编译,而且与STL保持一致,因为"所有STL容器也都提供有public成员函数和std::swap特化版本"
如果Demo是class template,那么情况就要发生变化,例如:
template<typename T> class Demo{ ... }
如果要采用以上方法,在Demo类模板内定义一个swap成员函数是可行的,但是要特化std::swap时势必要这样:
namespace std{ template<typename T> void swap<Demo<T> >(Demo<T>& lhs,Demo<T>& rhs){ lhs.swap(rhs); } }
结果通不过编译,因为C++标准目前只允许对class templates偏特化,而不允许对function templates偏特化(注:偏特化指的是将一个模板特化为另一个模板,例如部分类型参数特化以及将类型参数特化为容器类型参数(如上))
另一个方法是为std::swap添加一个重载版本,如下:
namespace std{ template<typename T> void swap(Demo<T>& lhs,Demo<T>& rhs){//注意没有swap之后<>,所以这是重载不是特化 lhs.swap(rhs); } }
不幸的是这也编译不通过,因为C++虽然允许对标准库templates进行特化,"但不可以添加新的templates(或classes或functions或其他任何东西)"到std里。
还有一种方法,与以上相同,声明一个non-member swap让它调用member swap,但不再将那个non-member声明为std::swap的特化或重载版本,而是置于另一个命名空间DemoStuff内,如下:
namespace DemoStuff{ ... template<typename T> class Demo{...} ... template<typename T> void swap(Demo<T>& lhs,Demo<T>& rhs){ lhs.swap(rhs); } }
当然,以上也可以声明在全局命名空间内,但是这样可能会造成作用域的杂乱无章(用《Effective C++中文版》来说,要保持"得体与适度").
3. 假设对于以下函数:
template<typename T> void doSomething{T& lhs,T& rhs){ ... swap(lhs,rhs); ... }
如果想要调用T的专属swap版本,并在该笨笨不存在的情况下,调用std内的一般化版本,可以按以下定义:
template<typename T> void doSomething{T& lhs,T& rhs){ using std::swap; ... swap(lhs,rhs); ... }
"一旦编译器看到对swap的调用,它们便寻找适当的swap并调用之.C++的名称查找法则(name lookup roles)确保将找到global作用域或T所在之命名空间内的任何T专属的swap."如果T是Demo并未与DemoStuff命名空间内,编译器会使用"实参取决之查找规则"(argument-dependent lookup)找出DemoStuff之内的swap."如果没有T专属之swap存在,编译器就调用std内的swap".(using std::swap并没有指定出现的swap是std内的版本,而是使std内的swap在函数内可见,如果按"std::swap(obj1,obj2)"的方式调用,调用的必是std内的版本)
3. 成员版(指的是以上的高效率版)swap绝不可抛出异常!正如以上所言,swap的一个重要应用是"帮助classes(或class templates)提供强烈的异常安全性(exception-safety)保障",但这一技术只适用于成员版(高效率版),因为默认版本的swap要调用拷贝构造函数和拷贝赋值操作符,而这两种函数都允许抛出异常.因此对于自定义的高效版本往往提供的不只是高效置换值的方法,而且是不抛出异常."一般而言这两种swap特性是连在一起的,因为高效率的swap总是基于对内置类型的操作,而内置类型上的操作绝不会抛出异常".