STL iterator和traits编程技法
今天终于看完了《STL源码分析》,最近忙于两个比赛的各种文档,没时间写东西,趁着看完的劲,把欠下的补上来。
《Design patterns》中对于iterator模式描述如下:提供一种方法,使之能够依序寻访某个聚合物所含的各个元素,而又无需暴露该聚合物的内部结构。在STL中,iterator扮演着连接container和algorithms的作用,下面以STL find()函数展现一下iterator在container和algorithm之间的连接作用。
template <class InputIterator, class T> InputIterator find (InputIterator first, InputIterator last, const T& value) { while(first != last && *first != value) ++first; return first; }
从代码中我们可以看到,find()函数接受两个具有指针行为的“智能指针”,他们“指向”我们所要处理的对象,可以通过提领(dereference)操作(*操作)来获取指向的内容,并可以通过++操作实现指向下一个功能。如我们现在有一个数组(容器)int a[5] = {1,2,3,4,5}; 我们可以这样使用find()。
int* p; p = find(a,a+5,3); cout << *p << endl; // 输出3
所以,iterator就是实现了指针的功能。但是,如果我们的数据容器变成set,由于set的内部结构是RB-tree,++操作就不能像native指针一样实现我们期许的指向下一个元素的功能,这时如果希望find()函数成功地工作在set上面,我们就必须封装set对外提供的“smart pointe”接口,以期达到我们期许的功能(例如提领操作取该迭代器指向的元素,member access操作,++操作指向下一个元素等)。为了隐藏容器的实现细节,迭代器由每一种容器的设计者来开发,于是每一种STL容器都提供了专属迭代器。
有时候,我们需要知道iterator所指对象的型别,用来定义对象。但这并不是一件简单的事。traits编程技法的作用就是用来获得iterator所指对象的型别。
如果要获得所指对象的型别,我们首先想到的是利用模板参数推到来实现。
template <class I, class T> void func_impl(I iter, T t){ T tmp; // do something } template<class I> inline void func(I iter){ func_impl(iter, *iter);// 只是简单的调用func_impl,通过传入*iter,通过模板推到,就可以知道iter所指类型了 }
但是这种方法有一个缺点就是“template参数推导”只能推导参数,而无法对函数返回值进行推导,所以STL用了内嵌型别来实现推导函数返回值型别。
template <class T> struct MyIter { typedef T value_type; T* ptr; MyIter(T* p = 0) : ptr(p) {} T& operator*() const {return *ptr;} }; template <class I> typename I :: value_type //通过内嵌类型确定函数的返回类型,其中typename用来告诉编译器value_type是一个型别,而不是I内成员函数或data member func(I ite){ return *ite; } MyIter<int> ite(new int(8)); // 确定了Myiter内嵌类型 value_type cout << func(ite);
出于泛化考虑,STL必须接受一个原生指针作为迭代器,上述方法不能处理原生指针的情况,故STL用偏特化的方法处理上述方法的缺点。
《泛型思维》对偏特化的定义为:针对template参数更进一步的条件限制所设计出来的一个特化版本。
上面的内嵌型别可以加一层间接性改成如下:
template<class I> struct iterator_traits{ typedef typename I::value_type value_type; }; template<class I> typename iterator_traits<I> :: value_type // I的内嵌型别value_type func(I tie){ return *ite; }
加一层间接性后就可以用偏特化来处理原生指针的情况。我们定义两个iterator_traits的偏特化版本。
template <class T> struct iterator_traits<T*>{ typedef T value_type; } template <class T> struct iterator_traits<const T*>{ typedef T value_type; }
这样不论是原生指针int*还是const int*,都可以通过iterator_traits萃取出他们正确的value_type。总结起来,iterator_traits就是利用内嵌型别和template模板参数推导,萃取iterator所指对象的型别。
iterator_traits不仅能够萃取value_type,每个iterator内嵌了五种型别,分别问value_type,difference type, reference type, pointer type, iterator_category,其中iterator_category表示的是iterator的分类。根据移动特性与施行操作,迭代器共分为五类:
input iterator(read only),output iterator(write only),forward iterator(++only),bidirectional iterator(++,--),random access iterator(涵盖所有指针运算p+n等)。以后的advance等算法会根据不同的迭代器类型重载不同实现函数以将效率发挥到最大!
总结起来,iterator提供了遍历容器的接口并尽可能的隐藏容器的实现细节。