STL源码分析读书笔记--第三章--迭代器(iterator)概念与traits编程技法
1.准备知识
- typename用法
- 用法1:等效于模板编程中的class
- 用法2:用于显式地告诉编译器接下来的名称是类型名,对于这个区分,下面的参考链接中说得好,如果编译器不知道 T::bar 是类型名的话 T::bar * p可能就被理解成了T::bar 乘以p,T::bar & p可能就被理解成为了 T::bar 和p做逻辑与操作。 事实上,在模板编程时,如果传入的模板参数为T(T里面有模板参数的非独立名字bar),那么在不显示指定的话,c++或假定T::bar为变量名以消除歧义。
- “A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.”
- 参考链接 : typename(wiki)
- traits编程技法(内嵌型别使用)
traits意为榨汁机,是在SGI 版本的STL实现中被广泛使用到的一个编程技法,需要使用的原因是我们不能根据一个不知道类型的变量直接推导出它的型别,听起来好像很奇怪,也就是不存在这么一个函数: typeof(),举个例子说明如下:
- 对于类型为 I 的变量 i ,I 可能是模板参数,如果对 I 来说 *操作有效,于是我们在程序中可以得到 *t ,然后需要将 *t 作为返回值返回,那么我们需要怎么做?下面问号的地方该怎么填?
我们经常需要在程序中获得迭代器相关的型别,由于迭代器与指针有某种类似的性质,所以迭代器所指型别很重要,经常需要在程序中使用,如上述问题之类的问题就被提出来了。这个问题的最终解决方案是---内嵌型别,对于刚才的函数,我们重新定义类 I ,在类 I 中声明一个内嵌型别,然后引用此内嵌型别,编译就可以通过,代码如下:
template<class I>?? func(I i){return *i;
}到目前为止,问题好像解决了,根据上面的原理,我们可以定义traits,专门获取迭代器所指类型的型别:
//迭代器雏形
template<class T>class myIter
{//... T's other member ,ingored
//T * ptr
//myIter(T * p=0):ptr(p){}
typedef typename T value_type;}template<class I>typename I::value_type func(I i)
{return *i;
}但是在刚才分析时我们看到,要定义内嵌型别,首先 I 必须是一个类,对于有些类型的迭代器,可能就是一个原生指针,无法定义内嵌型别,这个时候就需要用到偏特化了,偏特化的内容在我的另一篇文章中讲过。在那边文章中我称作部分特化,这里遵从hj先生的翻译而已。偏特化只是提供另一份template定义,意思是对于某些特殊的类别作为模板参数时,使用不同的模板定义式,这些特殊的类别一般都是原生指针,const原生指针,或者是多个模板参数一样,比如对于原生指针,一个可以接受的traits特化版本就是:
template<class I>struct iterator_traits
{typedef typename I::value_type value_type;//...
}同样,对于const类型的原生指针,如果它作为迭代器,也必须定义一个偏特化的traits版本:
template<class I>struct iterator_traits<I*>
{// typedef typename I value_type; (typename 可省略)
typedef T value_type;
//...
}
template<class I>struct iterator_traits<const T *>{//attention : const tag is removed because a value weith const type is useless.
typedef T value_type;
}
2.迭代器相关型别
迭代器相关的型别(侯捷先生翻译为相应型别,个人感觉不太准确)有五种:
- value type(所指型别)
- difference type(距离型别)
顾名思义,就是迭代器所指之物的型别,这个在上一节中作为例子讲过。
两个迭代器之间的距离
- reference type(所指型别的引用型别)
对于可以改变所指对象内容的迭代器,当需要引用所指对象时,用到的型别就是引用型别。
- pointer type(指针型别)
顾名思义
- iterator catalog(迭代器类型型别)
这个实现起来稍微复杂点,之所以要有这个型别,主要是效率考虑,对不同的类型迭代器,施加不同的算法以保证最高效率,书中给出的例子是advanced的实现根据不同的迭代器类型的不同变种。根据移动特性和施加的操作,迭代器被分为五类:
- input iterator
- output iterator
- foward iterator
- bidirectional iterator
- random acess iterator
这五种类型的iterator从上往下概念一次强化,从概念上来说,关系如下:
图中直线表示的是逐层强化的概念,而非继承关系,具体实现时,要表示不同型别的迭代器,在迭代器中要定义相应的非独立名称,这个非独立名称可以用来标识迭代器的种类,假设这五种非独立名称的定义如下:
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag:input_iterator_tag{};
struct bidirectional_iterator_tag:forward_iterator_tag{};
struct random_acess_iterator_tag:bidirectional_iterator_tag
它的具体操作使用过程是这样的,对于具体实现算法的函数,有一个参数是tag类参数,调用时,调用一个封装函数,封装函数根据迭代器萃取出迭代器类型型别参数,然后作为参数传递到具体实现的算法中。如下:
template<class InputIterator,class Distance>inline void __advance(InputIterator &i,Distance n,input_iterator_tag){while(n--)++i;
}template<class ForwardIterator,class Distance>inline void __advance(ForwardIterator &f,Distance n,forward_iterator_tag){//调用input_iterator_tag版本的
__advance(f,n,input_iterator_tag());}template<class BidirectionalIterator,class Distance>inline void __advance(Bidirectional &b,Distance n,bidirectional_iterator_tag){if(n>=0)while(n--)b++;else while(n++)b--;}template<class RandomAccessIterator,class Distance>inline void __advance(RandomAccessIterator &r,Distance n,random_access_iterator_tag){r+=n;}
然后加上一个对外封装的壳子如下,让编译器自动寻找最接近的实现函数:
template<class InputIterator,class Distance>inline void advance(InputIterator &i,Distance n){__advance(i,n,iterator_traits<InputIterator>::iterator_catagory());// 或许觉得__advance(i,n,InputIterator::iterator_catagory())说不定也可以通过,但是一个很显然的事情就是类似于原生指针这种形式就不可用了
}
3.std::iterator 的保证
任何iterator都应提供以上5种型别以供traits萃取,最好是继承下面的模板类:
template<class Catagory,class T,class Distance=ptrdiff_t,class Pointer=T*,class Reference=T&>struct iterator{
typedef Catagory iterator_catagory;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer_type;
typedef Reference reference_type;
}
4.总结
iterator是算法和数据结构之间的粘合剂,算法能统一地使用到stl的诸多数据结构中,iterator的设计功不可没,因此,对于stl中每一个容器,定好了底层的数据结构之后,设计相应的iterator就是接下来最重要的任务。(现在知道怎么回答iterator和指针的区别了吧)