1.STL中有swap()函数可以将两个对象的值相交换,定义类似于:
namespace std{
template<typename T>
void swap(T&a, T& b){
T temp(a);
a=b;
b=temp;
}
}
这个函数是异常安全性编程的核心,并且是用来处理自我赋值可能性的一个常见机制
2.可以使用swap()函数的多个版本:默认的STL中的swap(),类成员函数swap(),非成员函数swap(),std::swap()的特化版本等,总结来说就是:
- (1)如果默认的std::swap()的效率可以接受,则直接使用。但该版本涉及三个“复制”操作,某些情况下可能效率偏低。
- (2)如果std::swap()的效率不足(往往意味着类或模板类中使用了“以指针指向实现”(pointer to implementation, pimpl)的方法),可以尝试:
- 1)提供一个公有的swap()成员函数,使之高效交换该类型的两个对象值。此函数一定不能抛出异常:swap()函数的最好的应用是提供“异常安全性”。对其他情形没有这种限制。
- 2)在类或模板类所在的命名空间内提供一个非成员函数的swap(),并使之调用1)中的成员函数swap()。
- 3)如果正在编写类而非模板类,可以为该类“特化”std::swap(),并令它调用类的swap()成员函数。
- (3)如果调用swap(),一定要保证包含一个using声明式,以便让std::swap()在该函数内可见,然后不加任何命名空间修饰符,直接调用swap()函数。
3.所谓的“以指针指向实现”(pointer to implementation, pimpl),例如:
class WidgetImpl{ //针对Widget类而设计的类
public:
...
private:
int a, b, c;
std::vector<double> v; //可能有很多数据,复制起来很耗时
...
};
...
class Widget{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs){
...
*pImp = *(rhs.pImpl);
...
}
...
private:
WidgetImpl* pImpl; //指针,所指对象内含有Widget数据
};
如果用std::swap()交换两个Widget对象的值,不仅会复制三个Widget对象,而且还复制三个WidgetImpl对象。
4.在类中声明swap()成员函数:
class Widget{ //与前述定义相同,但增加swap()函数
public:
...
void swap(Widget& other){
using std::swap; //使用STL中的swap函数
swap(pImpl, other.pImpl); //只需替换指针即可
}
...
};
...
namespace std{ //通常不允许改变std命名空间内的任何东西,但是可以为标准库中的模板如swap()函数制特化版本,使之专属于自己的类
template<> void swap<Widget>(Widget& a, Widget& b){ //修订后的std::swap版本
a.swap(b); //若要转换Widget对象,则调用其成员函数的那个版本
}
}
5.所谓的“partially specialize“: C++允许对类模板进行”部分特化“,但不允许对模板函数进行”部分特化“。下面的定义就是错误的(将上例中的Widget类写成模板类):
namespace std{
template<typename T>
void swap<Widget<T>>(Widget<T>& a, Widget<T>& b){ //错误!不合法!
a.swap(b);
}
}
即使添加重载版本也行不通
namespace std{
template<typename T>
void swap(Widget<T>& a, Widget<T>& b){ //试图重载,不合法!
a.swap(b);
}
}
标准命名空间是一个特殊的命名空间,客户可以全特化里面的模板,但是不能添加新的模板(包括类模板和函数模板)到标准命名空间中去。
替代方案是:如果想让类专属版本的swap()被调用,需要在该类所处的命名空间内写一个类似上面的非成员函数版本以及一个std::swap()特化版本。。C++的名称查找法则(参数依赖查找法则或Koenig查找法则)能够找到Widget专属版本并调用。
注意在调用的时候不要画蛇添足地使用std::swap(obj1, obj2)这样的形式,直接按照函数名调用即可。