条款 25 :考虑写出一个不抛出异常的swap函数
Consider support for a non-throwing swap.
- 所谓swap(置换)两对象值,意思是将两对象的值彼此赋予对方。标准库缺省swap函数典型实现如下:
namespace std{
template<typename T>
void swap(T& a,T& b){
T temp(a);
a=b;
b=temp;
}
}
在上述代码中只要类型T支持copying,缺省的swap实现代码会自动完成对象的置换。观察上述代码,其涉及了三个对象的复制,但是细想,对于一些类型这些复制是没有必要的。若是非要这么写,无疑是降低了速度。其中最主要的形式就是”以指针指向一个对象,内含真正数据“即所谓的"pimpl手法"(pointer to implementation)我们来看下列例子:
class WidgetImpl{
public:
...
private:
int a,b,c;
std::vector<double> v;
...
};
class Widget{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs){
...
*pImpl=*(rhs.pIml);
...
}
...
private:
WidgetImpl* pImpl;
};
使用这个类我们一旦要置换两个Widget对象值,我们唯一需要做的就是置换其pImpl指针,但是问题是缺省的swap函数是不知道这一点的。明确告诉swap的具体实践是将std::swap对Widget特化。下面是基本思想:
namespace std{
template<>
void swap<Widget>(Widget& a,Widget& b){
swap(a.pImpl,b.pImpl);
}
}
上述函数一开始的template<>表示他是std::swap的一个全特化版本,函数之后的< Widget >表示这个特化版本是针对T是Widget而设计的。换一句话说就是:当一般性的swap用在Widget身上时就启用这个版本。通常情况下我们不能够(不被允许)更改std内的任何的任何内容,但是可以被允许为标准template制造特化版本。 我们说过上述代码还无法通过编译,原因是pImpl只private,而swap却妄想直接利用。解决之道就是,令特化的swap成为其friend.这里的声明方式与以往不太相同,我们的做法是:令Widget声明一个名为swap的public成员函数来做真正的置换工作,然后将std::swap特化,令他调用该函数成员:
class Widget{
public:
...
void swap(Widget& other){
using std::swap;
swap(pImpl,other.pImpl);
}
...
};
namespace std{
template<>
void swap<Widget>(Widget& a,Widget& b){
a.swap(b);
}
}
该做法不仅能通过编译,还与STL行为一致了。(具体不细说了) 然而当Widget和WidgetImpl都是calsses template而不是calsses时,我们也可以对WidgetImpl内的数据类型加以参数化:
template<typename T>
class WidgetImpl{...};
template<typename T>
class Widget{...};
想想上述情况我们的特化swap怎么写,看看下述代码:
namespace std{
template<typename>
void swap<Widget<T>>(Widget<T>& a,Widget<T>& b){
a.swap(b);
}
}
这看起来是合乎情理的,但是不合法。原因是:我们企图偏特化一份function template,但是C++只允许对calsses template偏特化,在function template上偏特化是行不通的。 当你打算偏特化一个函数模板,常用方法是为其添加一个重载版本,看下述代码:
namespace std{
template<typename>
void swap(Widget<T>& a,Widget<T>& b){
a.swap(b);
}
}
一般而言重载函数模板是没有问题的,但是std标准由C++标准成员会给出,他不允许添加你自己的function template版本。 所以上述代码依旧行不通。 为了解决这个问题,我们的做法是:我们声明一个non-member swap让他调用member swap,但这个non-member swap不再是std::swap的特化版本或重载版本。来看下列代码:
namespace WidgeStuff{
...
template<typename T>
class Widget{...};
...
void swap(Widget<T>& a,Widget<T>& b){
a.swap(b);
}
那么现在调用置换时,就会调用该空间的swap而不会影响std。
请记住
1. 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。 2. 如果你提供一个member swap,也应该提供一个non-member swap来调用前者。对于calsses(而非template),也请特化std::swap. 3. 调用swap时应该针对std::swap使用using 声明,然后调用swap并且不带任何”命名空间修饰资格“。 4. 为用户定义类型进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。
|