条款25:考虑写出一个不抛异常的swap函数
(Consider support for a non-throwing swap.)
内容:
swap函数就是相互交换两个对象的内容,标准程序库中已经提供了该函数的实现版本,其实现过程也是那么的如你预
期所愿:
namespace std{
template <typename T>
void swap(T& a,T& b)
{
T tempt(a);
a = b;
b = tempt;
}
}
但是这种实现版本对某些类型来说,效率上存在很大的问题,尤其面临那些对的性能要求很高的软件,这种实现版本
显然不能满足要求.这里我们所说的'某些类型'大体上指的是'以指针指向一个对象,内含真正数据'那种类型,这种
设计的常见的表现形式就是所谓的"pimpl"手法(条款31将提到).我们再来看一个例子:
class Window{ //这个class使用了pimpl手法
public:
Window(const Window& rhs);
Window& operator=(const Window& rhs){
...
*pImpl_ = *(rhs.pImpl_);
...
}
...
private:
class WindowImpl{
public:
...
private:
string title_;
std::vector<Window* > childList_;
};
WindowImpl* pImpl_;
};
如果用std::swap进行对两个Window对象进行交换的话,那么它不止复制三个Window还复制了三个WindowImpl对象,
非常缺乏效率.通常我们不能够(不被允许)改变std命名空间内的任何东西,但可以为标准templates制造特化版本,这里
我们可以为std::swap提供一个全特化版本来提高效率:
namespace std{
template<>
void swap<Window>(Window& a,Window& b){
swap(a.pImpl_,b.pImpl_);
}
}
但是这里遇到的问题是pImpl_是private域内成员变量,我们无法直接访问它,怎么办?我们的做法是构造一个member
swap的版本.
class Window{
public:
...
void swap(Window& other){
using std::swap;
swap(pImpl_,other.pImpl_);
}
...
};
namespace std{
template<>
void swap<Window>(Window& a,Window& b){
a.swap(b);
}
}
倘若这里的Window以及WindowImpl要都是class template而非class:
template<typename T>
class WindowImpl{...};
template<typename T>
class Window{...};
那么我们就不能这么写了:
namespace std{
template<typename T>
void swap<Window<T>>(Window<T>& lhs,Window<T>& rhs){
a.swap(b);
}
}
因为我们企图偏特化一个function template,但C++只允许对class template偏特化,在function template身上偏
特化是行不通的.那这么办?没有其它方法了吗?有!当你打算偏特化一个function template时,一般做法是简单地为它
添加一个重载版本,比如:
namespace std{
template<typename T>
void swap(Window<T>& lhs,Window<T>& rhs){ //这里是一个重载版本(没有<...>)
lhs.swap(rhs);
}
}
这样重载function templates没有问题,但std是个特殊的命名空间,你可以全特化std内的template,但你不能添加
心的template到std中,因为改命名空间内容完全由C++标准委员会决定,标准委员会禁止我们膨胀那些已经声明好的东
西.晕死,这也不行,那我们就自己创建命名空间不就行了:
namespace WindowStuff{
...
template<typename T>
class Window{...};
...
template<typename T>
void swap(Window<T>& lhs,Window<T>& rhs){
a.swap(b);
}
}
那接下来我们的客户如何使用呢?假设你正在写一个function template,其内需要置换两个对象值:
template<typename T>
void doSomething(T& obj1,T& ojb2){
using std::swap;
...
swap(obj1,obj2);
...
}
一旦编译器看到对swap的调用,它们便查找适当的swap并调用.如果T是Window并位于命名空间WindowStuff内的,
编译器会使用'参数依赖查找法'找到WindowStuff命名空间内的swap版本,如果该空间没有对应的swap版本,编译
器就使用std::swap(using的声明式让std::swap在函数内曝光),而在std空间内如果你提供了针对T的特化版本,编
译器就会挑中它,当然如果没有提供,那么编译器只有老老实实的调用那个一般化的swap版本的啰,呵呵.
现在来说一下我们的条款内容:成员版swap绝不可抛出异常.那是因为swap的一个最好的应用是帮助classes(和
class template)提供强烈的异常安全性保障.此约束只施行于成员版.
今天说的有点多了,如果没看懂的话,请慢慢消化.
请记住:
■ 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常.
■ 如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),
也请特化std::swap.
■ 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何""命名空间资格修饰.
■ 为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内加入某些std而言全新的东西.