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。这就涉及两个问题:
- T是一个真正的引用吗?
- 这两个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); } };
这样应该就可以高兴地收工了。