欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Effective C++ Term 26 实现一个不抛出异常的 swap

程序员文章站 2022-07-15 12:41:00
...

其实这条 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版本的优先级,仅仅是让它可见而已)

总结起来,其实我们有三件事要做

  1. 定义一个 public 函数swap,这个swap只接受一个参数
  2. 如果是针对一个,则需要在std空间中全特化swap
  3. 如果是针对一个模板类,则需要在模板类所在命名空间提供一个非成员函数的swap,这个swap接受两个参数

而不抛出异常的要求,则没有太多要注意的,因为我们实现一个高效swap的行为往往是在交换两个指针,而交换两个指针是不会引发异常的,这是由 C++ 针对内置类型作出的保证。只要这里的swap不引发异常,则我们可以根据这个特点,实现异常安全的操作(Term 29)

转载于:https://www.jianshu.com/p/5c6e4442f6a0