C++ 中交换两个值

本文中我们尝试以标准库编写者的身份实现C++标准程序库里最简单的算法之一:iter_swap。它卑微的职责就是接受两个迭代器并交换他们各自指向的对象的值,就像这样:

template<class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2) {
  T tmp = *i1;
  *i1 = *i2;
  *i2 = tmp;
}

显然这并不能编译通过,因为T没有被定义。那么,它到底应该是什么呢?

因为我们现在扮演的是标准库编写者,我们可以要求所有迭代器实现提供一个名为value_type的嵌套类型,这样就可以用它替换上文的T了:

template<class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2) {
  typename ForwardIterator1::value_type tmp = *i1;
  *i1 = *i2;
  *i2 = tmp;
}

如果你对这里的typename关键字迷惑不解,就应该先去看看C++标准。

到这里,我们应该可以实现这个算法了,遗憾的是,并没有。 C++中迭代器的设计是模仿指针的,这样做的用意是普通指针也可以用作合法的迭代器。

void f(int* p1, int* p2) {
  iter_swap(p1, p2); // error: int* 没有 value_type 成员
}

我们尝试一个几乎可以解决任何问题的方法——引入额外的中间层。我们无法向所有迭代器中添加嵌套的::value_type,但是可以将其添加到带有一个迭代器类型参数的模板。在标准中,这个模板称为 iterator_traits:

template<class Iterator> struct iterator_traits;

我们可以将它用在iter_swap中:

template<class Iterator>
struct iterator_traits {
  typedef typename Iterator::value_type value_type;
}
template<class T>
struct iterator_traits<T*> {
  typedef T value_type;
  //...
}

template<class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2) {
  typename iterator_traits<ForwardIterator1>::value_type tmp = *i1;
  *i1 = *i2;
  *i2 = tmp;
}

问题并没有就此结束。假设我们就这样设计iter_swap,不久就会收到大量来着关心性能的客户的issue,抱怨我们定义的iter_swap对某些迭代器来说效率低下得可怕,有个家伙传入了一个 std::list<std::vector<std::string>> 的迭代器。

幸运的是,标准库中提供了一个可以用于vector的高效的swap,它只交换少量的内部指针,我们可以回复用户这样就可以了:

std::swap(*i1, *i2);

然而这样的回答并不能令人满意。没什么iter_swap不去做这个工作而要交给用户呢?这就产生了另一个版本:

template<class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2) {
  std::swap(*i1, *i2);
}

看上去挺好,但是你应该注意到了,iter_swap可以交换两个不同的类型,二swap函数只能交换两个相同的对象。

我们可以这样来解决这个问题:让那个慢的iter_swap实现保持不动,同时加入一个重载版本,让相同类型的迭代器使用swap:

// 慢但总是可以
template<class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2) {
  typename iterator_traits<ForwardIterator1>::value_type tmp = *i1;
  *i1 = *i2;
  *i2 = tmp;
}

// 有时候更快
template<class ForwardIterator>
void iter_swap(ForwardIterator i1, ForwardIterator i2) {
  std::swap(*i1, *i2);
}

这样就可以交付使用了!

也许还有一些不够完美的地方。如果向iter_swap传入 std::vector<std::string> 和 std::list<std::string> 的迭代器会发生什么呢?这两个迭代器拥有相同的value_type,但是由于迭代器自身的类型不同,因此快速的iter_swap重载将不会被调用。我们可以对swap稍作改写:

template<class T1, class T2>
void swap(T1& a, T2& b) {
  T1 tmp = a;
  a = b;
  b = tmp;
}

不幸的是,这种修改会使一类迭代器无法使用,就是operator*重载产生一个代理引用的哪一种迭代器。最广为人知的就是 vector<bool> 的迭代器了。 vector<bool> 以一个位存储每一个元素。由于实际上并不存在指向一个位之类的东西,所以使用了一个代理。

最终,我们需要的是这样一种方式:只有当迭代器具有相同的value_type并且其引用类型是真正的引用而非代理时才去使用快速的iter_swap。这就涉及两个问题:

  1. T是一个真正的引用吗?
  2. 这两个value_type相同吗?

为了方便,我们直接使用Boost来做这样辅助作这个选择:

#include <boost/type_traits/is_reference.hpp>
#include <boost/type_traits/is_same.hpp>
#include <iterator> // iterator_taits
#include <utility>  // swap

namespace std {
  template<bool use_swap> struct iter_swap_impl;
  template<>
  struct iter_swap_impl<true> {
  template<class ForwardIterator1, class ForwardIterator2>
  static void do_it(ForwardIterator1 i1, ForwardIterator2 i2) {
  std::swap(*i1, *i2);
  }
  };
  template<>
  struct iter_swap_impl<false> {
  template<class ForwardIterator1, class ForwardIterator2>
  static void do_it(ForwardIterator1 i1, ForwardIterator2 i2) {
  typename iterator_traits<ForwardIterator1>::value_type tmp = *i1;
  *i2 = *i2;
  *i2 = tmp;
  }
  };

  template<class ForwardIterator1, class ForwardIterator2>
  void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2) {
  typedef iterator_traits<ForwardIterator1> traits1;
  typedef typename traits1::value_type v1; 
  typedef typename traits1::reference r1; 

  typedef iterator_traits<ForwardIterator2> traits2;
  typedef typename traits2::value_type v2; 
  typedef typename traits2::reference r2; 

  bool const use_swap = boost::is_same<v1, v2>::value
  && boost::is_reference<r1>::value
  && boost::is_reference<r2>::value;

  iter_swap_impl<use_swap>::do_it(i1, i2);
  }
};

这样应该就可以高兴地收工了。


By .