除了定义拷贝控制成员,管理资源的类通常还定义一个名为swap的函数。如果一个类定义了自己的swap,那么算法将使用类自定义版本。否则,算法将使用标准库定义的swap。
和传统的swap算法不一样,我们更希望使用指针交换的方式来实现类的交换:
string *temp=v1.ps; //为v1.ps中的指针创建一个副本
v1.ps=v2.ps; //将v2.ps中的指针赋予v1.ps
v2.ps=temp; //将保存的v1.ps中原来的指针赋予v2.ps
编写我们自己的swap函数
swap的典型实现:
class HasPtr {
friend void swap(HasPtr&,HasPtr&);
//其他成员定义
};
inline
void swap(HasPtr &lhs,HasPtr &rhs)
{
using std::swap;
swap(lhs.ps.rhs.ps); //交换指针,而不是string数据
swap(lhs.i,rhs.i); //交换int成员
}
swap函数应该调用swap,而不是std::swap
上面代码有一个很重要的地方:虽然在这个地方不重要,但在一般情况下它非常重要——swap函数中调用的swap不是std::swap。在本例中,数据成员是内置类型的,而内置类型是没有特定版本的swap的,所以在本例中,对swap的调用会调用标准库std::swap。
但是,如果一个类的成员有自己类型特定的swap函数,调用std::swap就是错误的了。假如我们有另一个命名为Foo的类,它有一个类型为HasPtr的成员h。如果我们未定义Foo版本的swap,那么就会使用标准库版本的swap。我们可以为Foo编写一个swap函数来避免这种情况。
在赋值运算符中使用swap
定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为拷贝并交换的技术。这种技术将左侧运算与右侧运算对象的一个副本进行交换:
//注意rhs是按值传递的,意味着HasPtr的拷贝构造函数
//将右侧运算对象中的string拷贝到rhs
HasPtr& HasPtr:;operator=(HasPtr rhs)
{
//交换左侧运算对象和局部变量rhs的内容
swap(*this,rhs); //rhs现在指向本对象曾经使用的内存
return *this; //rhs被销毁,从而delete了rhs中的指针
}
这个技术的有趣之处是它自动处理了自赋值情况且天然就是异常安全的。它通过在改变左侧运算对象之前拷贝右侧运算对象保证了自赋值的正确,这与我们在原来的赋值运算符中使用的方法是一致的。它保证异常安全的方法也与原来的赋值运算符实现一样。代码中唯一可能抛出异常的是拷贝构造函数中的new表达式。如果真发生了异常,它也会在我们改变左侧运算对象之前发生。