Effective C++ Term 26 实现一个不抛出异常的 swap
其实这条 Term 的重点不在于抛出异常,而在于怎么实现一个高效的swap
,不抛出异常的要求是相对比较好保证的
标准库中swap
的实现大概是这样的
namespace std {
template <typename T>
void swap(T &a, T &b) {
T temp(a);
a = b;
b = temp;
}
其中涉及到一次拷贝构造,两次赋值操作,如果T
中包含的数据量比较大,这种swap
操作开销是相当大的,因此我们有的时候需要定制自己的swap
,如对于下面这个类
class Widget {
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) { *pImpl = *(rhs.pImpl); }
private:
WidgetImpl* pImpl;
};
实际上只要swap
两个指针就可以了,因为赋值操作语句的开销是比较大的(如果*pImpl
指向的数据块比较大的话),对于一个类,我们可以在std
命名空间中对swap
函数进行特化,即这个特化的函数的优先级高于默认的 general 的swap
class Widget {
public:
void swap(Widget& other) {
std::swap(pImpl, other.pImpl); // for pointers, using std::swap is efficient
}
};
namespace std {
template <>
void swap<Widget>(Widget& lhs, Widget& rhs) {
a.swap(b);
}
} // namespace std
这段代码中,我们先定义一个 member function 的 swap
,再让 namespace std
中的swap
来调用这个 member function,从而达到定制化的目的
对于模板类,情况发生了变化,因为 C++ 不允许函数偏特化,只允许函数全特化。
由于 C++ 对于namespace std
的特殊约束,无论是这样的偏特化
namespace std {
template <typename T>
void swap<Widget<T> >(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
} // namespace std
还是这样的模板重载
namespace std {
template <typename T>
void swap(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
} // namespace std
都是非法的(这种非法只针对于namespace std
而言,其他命名空间不受此限制)
那么解决办法也很明显,既然上述约束对于其他命名空间无效,我们只要将swap
定义在我们自己的命名空间就可以了
namespace WidgetStuff {
template <typename T>
class Widget { // ...
};
template <typename T>
void swap(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
} // namespace WidgetStuff
由于 C++ 特殊的搜索顺序,它在调用swap
时,它会优先在Widget
所在命名空间去寻找,如果找不到,才会继续在std
空间中寻找,因此,在我们调用时,只需要这样写
template <typename T>
void doSomething(T& obj1, T& obj2) {
using std::swap; // make std::swap available in this function
swap(obj1, obj2); // call the best swap for objects of type T
}
就可以达到正确调用的目的。这段代码中,using std::swap
的作用是很重要的,因为作为 client,我们其实也不是很清楚有没有针对Widget
的定制版本swap
,因此先加上一句using std::swap
,允许std
空间的swap
可见,再让编译器去寻找参数匹配最优的版本(using std::swap
不会让编译器提高std::swap
版本的优先级,仅仅是让它可见而已)
总结起来,其实我们有三件事要做
- 定义一个 public 函数
swap
,这个swap
只接受一个参数 - 如果是针对一个类,则需要在
std
空间中全特化swap
- 如果是针对一个模板类,则需要在模板类所在命名空间提供一个非成员函数的
swap
,这个swap
接受两个参数
而不抛出异常的要求,则没有太多要注意的,因为我们实现一个高效swap
的行为往往是在交换两个指针,而交换两个指针是不会引发异常的,这是由 C++ 针对内置类型作出的保证。只要这里的swap
不引发异常,则我们可以根据这个特点,实现异常安全的操作(Term 29)
转载于:https://www.jianshu.com/p/5c6e4442f6a0