条款25:考虑写出一个不抛异常的swap函数
Consider support for a non-throwing swap.
在上一部分里,我们明白swap算法无法知道pImpl指针,而我们希望告诉std::swap,当Widgets被置换时,真正应该做的就是置换内部的pImpl指针。
而实现这一想法的做法是
- 将std::swap针对Widget特化。
下面的代码是思路的实现,但是无法通过编译:
namsespace std { //这是std::swap针对“T是Widget”的特化版本,并不能通过编译
tempate<>
void swap<Widget>(Widget& a, Widget& b)
{
swap(a.pImpl, b.pImpl); //置换Widget时,只需要置换它们的pImpl指针即可
}
}
对于这个无法编译函数:
- tempate<>:表示它是std::swap的一个全特化(total template specialization)版本;
- 函数名之后的:表示这一特化版本会针对“T是Widget”而设计的;
换句话说,一般性的swap template施加于Widget身上便会启用这个版本。
通常而言,我们不能改变std命名空间内的任何东西,但是可以为标准templates(如swap)制造特化版本,使得它专属于我们自己定义的class(例如Widget)。上面的代码也正是这样去实现的。
而之所以上面的代码无法通过编译,是因为:
- 它企图访问a和b内的pImpl指针,但这个指针是private。
成员函数swap
因此,一个解决办法则是:
- 令Widget声明一个名为swap的public成员函数,来去做真正的置换工作,然后将std::swap特化,令它调用该成员函数:
class Widget { //与前面相同,唯一的差别就是增加swap函数
public:
...
void swap(Widget& other)
{
using std::swap; //这个声明是非常必要的
swap(pImpl, other.pImpl); //若要置换Widget,就置换其pImpl指针
}
...
};
namespace std { //修订后的std::swap特化版本
tempate<>
void swap<Widget>(Widget& a, Widget& b)
{
a.swap(b); //如果要置换Widgets,调用其swap成员函数
}
}
在上面的代码中,不仅能够通过编译,而且还与STL容器有一致性:
- 所有STL容器也都提供有public swap成员函数和std::swap特化版本(用以调用前者)
特偏化(partially specialize)
然而,假设Widget和WidgetImpl**都是class template而并非class**,也许可以尝试将WidgetImpl内的数据类型加以参数化:
template<typename T>
class WidgetImpl { ... };
temolate<typename T>
class Widget { ... };
在Widget内放一个swap成员函数就像前面一样简单,但是在特化std::swap时却会遇到问题!
namespace std {
template<typename T>
void swap< Widget<T> > ( Widget<T>& a, //错误!
Widget<T>& b)
{ a.swap(b); }
}
虽然这样看起来是合理的,然而却并不合法。
当我们企图特偏化(partially specialize)一个function template(std::swap),而C++只允许对class template特偏化,在function template身上特偏化是不可以的。
当我们打算偏特化一个function template时,一般的做法是简单地为它添加一个重载模板:
namespace std {
template<typename T> //std::swap的一个重载版本
void swap(Widget<T>& a, //需要注意的是,swap后面没有" <...> "
Widget<T>& b) //但是这样也是不合法的!
{ a.swap(b); }
}
一般而言,我们是可以重载function template的,但是std是一个特殊的命名空间,因此管理规则也比较特殊:
- 使用者可以全特化std内的template,但是不可以添加新的template(或者class、function以及其他东西)
因此,所谓的“禁止”,其实添加新东西到std里是可以编译的,但是这样的行为是没有明确定义的。
- 如果我们希望程序有可预期的行为,就不要添加任何新东西到std之中。