引言
引用侯捷在《STL源码剖析》中的话——STL的中心思想中于:将数据容器(contrainers)和算法(algorithms)分开,彼此独立设计,最后再以一贴胶着剂将它们撮合在一起。而Iterator正是扮演了这个撮合的关键角色。从两本权威资料《C++标准程序库》和《STL源码剖析》的解读,以及SGI中Iterator的源码实现来看,我觉得这个分离是在相当多的前提下面进行的。在这方面,标准库做了足够的接口规定以及解析说明,这两天看这些内容,再次深深体会到库设计所体现出来的对各方面的技术的要求。
《C++标准程序库》 http://download.csdn.net/detail/eagleatustb/4653134
《STL源码剖析》http://download.csdn.net/detail/eagleatustb/3037915
SGI STL官方网站 http://www.sgi.com/tech/stl/index.html
以下是我的一些宏观的总结,对于我是花了两天的时间,对于解读过源码的朋友应该有用。我的学习方法是首先对着《STL源码剖析》看源代码+程序调试运行,这个时候有不少关于接口的东西不明白,会觉得这个设计为什么仅仅写一个空类型,为什么定义那么多别名;带着问题,再看《C++标准程序库》,里面会把设计的目的亮出来,并且说明它需要到达怎样的使用效果!这个时候,很多疑问就解开了;回过头来,对着代码和两本书,写下这篇文章。
这里不打算把Iterator的代码一一解析,因为相关的内容太抽象,需要不停的描述相应的容器及算法内容。
迭代器概要列表
1. 五个基本迭代器类型:
分别使用以下定义类型标记其类型
2. 七种迭代器的适配器:
reverse_bidirectional_iterator
3. 三个通用的方法
iter_swap
逻辑理解
对于代码的逻辑的理解步骤,我们遵从一个原则:先确定行为,再确定对象,然后弄清概念,最后把握全局。
行为
迭代器(准确的说是迭代器适配器)的功能是适应不同的容器进行通用的迭代操作,如operator(int)++,operator++,operator*,operator&等;然而,每种窗口的operator++行为又可能是不一样的,比如:vector的operator++行为只需要原指针加1就可以了,而tree(包括set,map等使用红黑树原理实现的容器)的operator++行为就需要找到下一个节点,可能是子节点,可能是父节点。
那需要怎么办呢?对,在不同的容器里作不同的实现。使用虚函数吗?不,STL那帮人对虚函数的开销很敏感;你当然就想到了Template,对的,就这么实现。
上面不是说过准确的说是迭代器适配器吗?这个适配器应该有什么样的行为呢?像这样!
std::list<int>L;
L.push_front(3);
L.insert(L.begin(),5);
std::back_insert_iterator<std::list<int> > ii(L);
*ii++ = 0;
*ii++ = 1;
*ii++ = 2;
back_insert_iterator是一个迭代器适配器,它适配带有从后插入功能的容器(vector,list有从后插入功能),然后在适配器上做一般的移动和插入操作(back_insert_iterator的operator=的实现调用容器中的push_back操作在list中创建了节点并插入链表中)。这些实际的push操作又需要list本身的迭代器(这是真正意义上的迭代器)有end()这个操作找到尾端来做插入工作。
对象
通过上面的解析,可能还不太明白,这里确定一下对象。
容器:像vector,list,deque,set,map这样的,大家应该很容易就理解。
迭代器:其实现根据每个容器的节点构造去实现,对应也就会有_List_iterator,_Deque_iterator,_Rb_tree_iterator 这些类,这些类的设计实现一般放在上面所说的容器的类实现文件中。拿list的迭代器出来溜溜吧:
template<class_Tp,class_Traits>
struct_List_iterator :public_List_iterator_base {
typedef_Tpvalue_type;
typedeftypename_Traits::pointer pointer;
typedeftypename_Traits::reference reference;
typedef_List_iterator<_Tp,_Traits> _Self;
typedeftypename_Traits::_NonConstTraits _NonConstTraits;
typedef_List_iterator<_Tp,_NonConstTraits>iterator;
typedeftypename_Traits::_ConstTraits _ConstTraits;
typedef_List_iterator<_Tp,_ConstTraits> const_iterator;
typedefbidirectional_iterator_tagiterator_category;
typedef_List_node<_Tp>_Node;
typedefsize_tsize_type;
typedefptrdiff_tdifference_type;
explicit_List_iterator(_List_node_base*__x) : _List_iterator_base(__x) {}
_List_iterator() :_List_iterator_base(0){}
//copy constructor for iteratorand constructor from iterator for const_iterator
_List_iterator(constiterator&__x) : _List_iterator_base(__x._M_node) {}
referenceoperator*()const { return__STATIC_CAST(_Node*,this->_M_node)->_M_data; }
_STLP_DEFINE_ARROW_OPERATOR
_Self&operator++() {
this->_M_incr();
return *this;
}
_Selfoperator++(int) {
_Self__tmp = *this;
this->_M_incr();
return__tmp;
}
_Self&operator--() {
this->_M_decr();
return *this;
}
_Selfoperator--(int) {
_Self__tmp = *this;
this->_M_decr();
return__tmp;
}
booloperator==(const_iterator__y ) const {
returnthis->_M_node ==__y._M_node;
}
booloperator!=(const_iterator__y ) const {
returnthis->_M_node !=__y._M_node;
}
};
看到了吧?把很多的operator都重载掉了,因为它是特别为list设计的,针对_Node(也就是list的节点)这个类型的。从这里看来,这个iterator是根据list的节点去设计的,只能list使用;其他的容器中的iterator也是同样的道理,为这个容器设计的迭代器只能这个容器来使用。
这里有必要说明一下,vector有点特殊,没有单独设计vector_iterator,为什么呢?因为没有必要,如果看过vector的源代码的人就清楚,vector的元素是一个挨着一个连续存放的,其元素类型是value_type,只需要把iterator定义为元素类型的指针就可以了,它的operator++等操作完全就是value指针的对应操作,连重载的必要都没有。不信?看一下它的定义:
template <class_Tp,class_Alloc =__STL_DEFAULT_ALLOCATOR(_Tp) >
classvector :protected_Vector_base<_Tp,_Alloc>
{
private:
typedef_Vector_base<_Tp,_Alloc>_Base;
public:
typedef_Tpvalue_type;
typedefvalue_type*pointer;
typedefconstvalue_type*const_pointer;
typedefvalue_type* iterator;
typedefconst value_type*const_iterator;
typedefvalue_type&reference;
typedefconstvalue_type&const_reference;
typedefsize_tsize_type;
typedefptrdiff_tdifference_type;
。。。
}
迭代器适配器:按上面的描述,容器也有了,也为每个容器量身订做了一个迭代器,直接通过容器操作去使用不就可以了吗? 通常的确是这样,比如:
std::list<int>L;
L.push_front(3);
L.push_back(6);
std::list<int>::iteratorlist_it = L.begin();
L.insert(list_it,5);
的确,我以前用迭代器也都是这么用的,我目前也没在实践中用过迭代器适配器。但是有的情况,如果需要在不知道迭代器是哪个容器的迭代器的前提下做功能的实现,比如算法中的copy函数,实现功能是这样的:输出源起始点,源结束点,目标起始点;把从源起始点到源结束点的数据复制到目标起始点。为实现其通用性,以上三个参数用迭代器来传入。而程序根本不知道要传入的这个迭代器是哪个容器的迭代器,这个情况下,适配器就显得必要了。以下说明一下源代码:
这里是对外接口,检查一下数据,丢给__copy_aux做真正的事情。
template <class_InputIter,class_OutputIter>
inline_OutputItercopy(_InputIter__first,_InputIter__last,_OutputIter__result) {
_STLP_DEBUG_CHECK(_STLP_PRIV__check_range(__first,__last))
return_STLP_PRIV__copy_aux(__first,__last, __result, _BothPtrType< _InputIter, _OutputIter>::_Answer());
}
__copy_aux会根据传进来的迭代器,区分ITERATOR_CATEGORY,也就是迭代器的基本类型。进而调用对应ITERATOR_CATEGORY的处理函数。
template <class_InputIter,class_OutputIter>
inline_OutputIter__copy_aux(_InputIter__first,_InputIter__last,_OutputIter__result,
const__false_type&/*BothPtrType*/) {
return_STLP_PRIV__copy(__first,__last, __result,
_STLP_ITERATOR_CATEGORY(__first,_InputIter),
_STLP_DISTANCE_TYPE(__first,_InputIter));
}
如果传入的是list容器的迭代器,其iterator_category是bidirectional_iterator_tagiterator_category(回头看一看上面的list迭代器定义里面typedefbidirectional_iterator_tagiterator_category;),相应就调用bidirectional_iterator的copy实现。
template <class_InputIter,class_OutputIter,class_Distance>
inline_OutputIter__copy(_InputIter__first,_InputIter__last,
_OutputIter__result,constinput_iterator_tag &,_Distance*) {
for ( ;__first !=__last; ++__result, ++__first)
*__result = *__first;
return__result;
}
咦?为什么贴出来的是constinput_iterator_tag&?我没有搞错,因为在copy这个层面,input_iterator_tag和forward_iterator_tag,bidirectional_iterator_tag的实现是一样的,SGI的实现在_STLP_ITERATOR_CATEGORY这个宏做了小手脚,把相同的实现放取一起调用。而如果是vector的话,会调用以下的copy:
template <class_RandomAccessIter,class_OutputIter,class_Distance>
inline_OutputIter
__copy(_RandomAccessIter__first,_RandomAccessIter__last,
_OutputIter__result,constrandom_access_iterator_tag &,_Distance*) {
for (_Distance__n =__last -__first;__n >0; --__n) {
*__result = *__first;
++__first;
++__result;
}
return__result;
}
因为vector是属于随机存储容器random_access_iterator_tag。
概念
一般来说,上面的行为和对象说了这么多,读者应该晕的差不多了;现在来理清一些概念,再返回上面去理解,会是一种不错的方法。
容器按结构分类分为顺序容器和关联容器,顺序容器包括vector,list,deque;关联容器包括set,multiset,map,multimap(不把hashtable算进来,它不在STL标准容器的规范里面)。
每个容器都会实现其容器迭代器(之前说过了vector没有的原因),而迭代器的种类有五种:
input_iterator_tag
output_iterator_tag
forward_iterator_tag
bidirectional_iterator_tag
random_access_iterator_tag
这五种迭代器不是排斥的关系,而是下面的各类对上面的各类的加强的关系。其实这些是空结构,只是为了后面用来区分类型使用的(参数里只有类型,不传变量,在编译时编译器直接确定调用的实现函数,避开虚函数开销),也算是一种高级一点的编程技巧,学会了以后用得着的。
structinput_iterator_tag {};
structoutput_iterator_tag {};
structforward_iterator_tag :publicinput_iterator_tag {};
structbidirectional_iterator_tag :publicforward_iterator_tag {};
structrandom_access_iterator_tag :publicbidirectional_iterator_tag {};
以上说过的容器中所用的迭代器都有一个标志类型的符号iterator_category。
template <class_Category,class_Tp,_STLP_DFL_TMPL_PARAM(_Distance,ptrdiff_t),
_STLP_DFL_TMPL_PARAM(_Pointer,_Tp*),_STLP_DFL_TMPL_PARAM(_Reference,_Tp&)>
structiterator {
typedef_Category iterator_category;
typedef_Tp value_type;
typedef_Distance difference_type;
typedef_Pointer pointer;
typedef_Referencereference;
};
所有迭代器都继承了这个只有类型定义的结构,是STL规定的接口所需。
现在可以对容器的迭代器分一个类了:
Vector::iterator因为vector实现为随机读取的数据结构,可以进行一切迭代操作,为random_access_iterator_tag;
list::iterator 因为set实现为链式的双向链结构,不可以随机读取,但可以双向操作,为bidirectional_iterator_tag;
而Set,multiset,map,multimap底层都是通过二叉树实现的,二叉树可以双向遍历,对应也就可以双向迭代操作了,为bidirectional_iterator_tag;
还有之前没有提到过的istream也是容器,是input_iterator_tag,ostream也是容器,属于output_iterator_tag,这两个相对比较特殊,可以另开一篇做解析,这里不详述。
到了这个时候,可以说一说七种适配器的类型了,这七种类型都是按意义命名的,不解析名字了。
istream_iterator:只给istream容器用,只读不写。
ostream_iterator:只给ostream容器用,只写,读不准。
reverse_iterator:有双向功能迭代器的容器都可以使用。
reverse_bidirectional_iterator:有双向功能迭代器的容器都可以使用。
insert_iterator:实现了insert操作的容器都可以用,就是所有容器啦
front_insert_iterator:实现了push_front操作的容器可用,vector、关系容器没实现push_front操作(为什么?哈,想一想),所以不能用;list,deque实现了此操作,可以使用。
back_insert_iterator:实现了push_back操作的容器可用,关系容器没实现此操作,不能用。
源码
对于一些实现上的小技巧,本人也很少使用,为免讲错,请参考侯先生的书。里面有详细的说明。而对于每个容器里面的迭代器的实现,是根据每个容器的结构特点做的,这里也不一一对各个容器进行分析,如果这么做了,势必喧宾夺主,变成了容器的设计分析了。这里从分析一个迭代器适配器入手,展示其结构原理。最好是你应该已经了解GOF的设计模式中的Adapter模式。
template <class_Container>
classfront_insert_iterator
: publiciterator<output_iterator_tag,void, void, void,void>{
typedeffront_insert_iterator<_Container>_Self;
protected:
//c is a Standard name(24.4.2.3), do no make it STLport naming convention compliant.
_Container *container;
public:
typedef_Container container_type;
typedefoutput_iterator_tagiterator_category;//汗一个,别问我为什么是output,我也不知道!
explicitfront_insert_iterator(_Container&__x) : container(&__x) {}
_Self&operator=(const_Self&__other) {
container =__other.container;
return *this;
}
_Self&operator=(consttypename_Container::value_type&__val) {
container->push_front(__val);//增加新元素的操作由容器实现
return *this;
}
_Self&operator*() {return *this; }
_Self&operator++() {return *this; }//实现无效的++操作,为的是迎合公共接口
_Self operator++(int) {return *this; }//实现无效的++操作,为的是迎合公共接口
};
template <class_Container>
inlinefront_insert_iterator<_Container> _STLP_CALLfront_inserter(_Container&__x)
{ returnfront_insert_iterator<_Container>(__x); }
总结
一般来说,对于科学的学习方法,应该是从点、线、面一一深入。但有时候如果能综合运用自顶向下的学习方法,也能收到一定的效果。