Effective C++学习笔记总链接
改善程序与设计的55个具体做法学习笔记-每日1条
条款25:考虑写出一个不抛异常的swap函数
【技巧】
1. 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
2. 如果你提供一个member swap,也该提供一个non-member swap用来调用前者,对于class(而非template),也请特化std::swap。
3. 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。(利用名称查找法则 为“T”型对象调用最佳的swap)
4. 为“用户定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。
典型的swap
swap是个很有用的函数,它是STL的一部分,后来成为异常安全性编程的脊柱,以及用来处理自我赋值可能性的一个常见机制。
swap(置换)两对象值,意思是将两对象的值彼此赋予对方。
其典型实现代码如下:
namespace std
{
template<typename T>
void swap(T& a, T& b) // 置换a和b的值
{
T temp(a);
a = b;
b = temp;
}
}
缺省的swap的问题?
且看下面代码
class WidgetImpl
{
public:
...
private:
int a, b, c; //可能有许多数据,
std::vector<double> v; //意味复制时间很长。
...
};
class Widget
{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& ths) //复制Widget时,令它复制其WidgetImpl对象
{
...
*pImpl = *(rhs.pImpl);
...
}
...
private:
WidgetImpl* pImpl;
};
一旦要置换两个Widget对象值,我们唯一需要做的就是置换其pImpl指针,但缺省的swap算法不知道这一点。它不只复制三个Widgets,还复制三个WidgetImpl对象,非常缺乏效率!
我们希望能够告诉std::swap,当Widget被置换时真正该做的是置换其内部的pImpl指针。
解决办法:将std::swap针对Widget特化
【注意】:通常我们不能够(不被允许)改变std命名空间内的任何东西,但可以(被允许)为标准template(如swap)制造特化版本,使它专属于我们自己的class。
具体做法:令Widget声明一个名为swap的public成员函数做真正的置换工作,然后将std::swap特化,令它调用该成员函数
class Widget // 与前同,唯一的区别是增加swap函数
{
public:
...
void swap(Widget& other)
{
using std::swap; // 这个声明是必要的
swap(pImpl, other.pImpl); // 若要置换Widget就是置换其pImpl指针
}
...
};
namespace std
{
template<> // 这是std::swap针对“T是Widget的特化版本”
void swap<Widget>(Widget& a, Widget& b)
{
a.swap(b); //若要置换Widget,调用其swap成员函数。
}
}
值得说明的是,
- template<> 表示它是std::swap的一个全特化版本
- 函数名称之后的“< Widget>”表示这一特化版本系针对“T是Widget”而设计
- 上述代码与STL容器有一致性,所有STL容器也都提供public swap成员函数和std::swap特化版本
针对Widget 是class template的swap, 等复习了template后补充。
总结:
首先,如果swap的缺省实现码对你的class或class template提供可接受的效率,不需要额外做任何事。
其次,如果swap缺省实现版的效率不足(一般你的class使用了某种pimpl手法(指向实现者的指针))尝试做以下事情:
- 提供一个public swap成员函数,让它高效地置换你的类型的两个对象值,这个函数绝不该抛出异常。
- 在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。
- 如果你正编写一个class(而非class template),为你的class特化std::swap,并令它调用你的swap成员函数。
最后如果你调用swap,请确定包含一个(using std::swap)声明式,以便让std::swap在你的函数内曝光,然后不加任何namespace修饰符,赤裸裸地调用swap。