swap原本只是STL的一部分,后面成为异常安全编程的脊柱,及处理自我赋值安全性的一个常见机制。
例子:标准程序库提供的swap算法
namespace s td{
template<typenameT>
void swap(T& a,T& b) //std::swap的典型实现
{
T tmp(a);
a=b;
b=tmp;
}
}
要求:类型T支持copying(通过copying构造函数和copyassignment操作符完成)
上述代码主要涉及三个对象的复制,但对于某些类型而言,复制并不重要。
如pimpl手法(point to implementation)”以指针指向一个对象,内含真正数据”
class WidgetImpl //针对widget设计的类
{
public:
...
private: //数据多,复制时间长
inta,b,c;
std::vector<double>v;
...
};
class Widget //采用pimpl手法
{
public:
Widget(constWidget& rhs);
Widget&operator=(const Widget& rhs) //复制Widget时 令其复制WidgetImpl对象
{
...
*Impl=*(rhs.Impl);
...
}
private:
WidgetImpl*Impl; //指针,内含Widget数据
};
转换述对象,只需置换pImpl指针,相对比复制对象效率更高。
为了让std::swap知道置换Widget时是置换指针pImpl,可行的方法是将std::swap针对Widg特化,如下构想。
namespace std
{
template<>
voidswap<Widget>(Widget& a,Widget& b) //std::swap针对“T是Widget”的特化版本
{
swap(a.Impl,b.Impl); //很明显不能编译通过,因为没有访问private的权限
}
}
“template<>”表明它是std::swap的一个全特化版本(total template specialization),函数名后的<Widget>表示这一特化版本是针对“T是Widget”而设计。(一般性的swap施行于Widget便会启用这个版本)
通常不允许改变std命名空间内的任何东西,但允许为标准的templates(如swap)制造特化版本,使它专属于某个类(如Widget)
上述不编译不能过,我们可以置其为友员。
还有解决方法是:令Widget声明一个名为swap的public成员函数做真正的置换工作。然后将std::swap特化,令它调用该成员函数,如下:
class Widget //同前,唯一差别是增加了swap
{
...
voidswap(Widget& other)
{
uingstd::swap;
//如果没有专属swap,就使用std内的swap,(这就是函数内声明std::swap的原因)
swap(Impl,other.Impl); //只需交换指针
}
...
};
namespace std
{
template<>
voidswap(Widget& a,Widget& b) //修订后的std::swap特化版本
{
a.swap(b); //置换Widget,调用其成员函数
}
}
这种作法能通过编译,还与STL有一至性:因为所有STL容器提供有public swap成员函数和std::swap的特化版本。
当是类模版而非类时,如下代码:
template<typename T>
classWidgetImpl{...};
template<typename T>
classwidget{...};
namespace std
{
template<typenameT>
voidswap<Widget<T>>(Widget<T>& a,Widget<T>& b) //错误,不合法
{
a.swap(b);
}
}
看似合理,实则错误。因为上面我们企图偏特化(partially specialize)一个函数模板(function template(std::swap)),C++仅允许对类模板偏特化。
当偏化一个函数模版时,惯常做法是简单的函数重载,如下代码,但是也是不合法的、
namespace std //std::swap的一个重载版本
{
template<typenameT> //swap后面不跟“< >”
voidswap (Widget<T>& a,Widget<T>& b) //错误,不合法
{
a.swap(b);
}
}
一般重载函数模版没问题,但std是特殊的命名空间。标准委员会禁止膨胀那些已经声明好的东西。也就是说:我们可以全特化std内的template,但不能添加任何新的东西(模版或class或functions等)。
解决方法:声明一个non-member的swap调用member swap,但不再将non-member声明为std::swap的特化或重载版本。假设所有widger机能位于名字空间WidgetStuff内,如下代码:
namespaceWidgetStuff
{
... //WidgerImpl类同前
classWidget{...}; //类含swap成员函数
template<typenameT>
voidswap<Widget<T>>(Widget<T>& a,Widget<T>& b)
//非成员函数,但不属于std空间
{
a.swap(b);
}
}
现在任何地点置换两个widget对象,C++会根据名称查找法则(name lookuprule)找到WidgetStuff内专属版本。
如果想让“class专属版”swap在更多情况下适用,应在该class所在命名空间内写一个non-menber版本和std::swap特化版本
对于default swap,member swap, non-member swaps,std::swap特化版本做个总结:
如果默认swap效率好,则使用缺省swap,反之,则、
1、提供一个public成员函数,高效转换对象值。(但这个函数绝不能抛出异常)
2、如果提供一个member swap,也应该提供一个non-memberswap来调用前者。对class(而非类模版),特化std::swap。
3、调用swap时应针对std::swap使用using 声明式,然后调用swap不带任何“命名空间资格修饰符”。
4、对“用户定义类型”进行stdtemplate全特化。但不能在std内加入全新东西。