为什么要有萃取技术 既然迭代器是一种智能指针,iterator
也要对一个原生指针进行封装,而问题就来源于此,当我们需要这个原生指针所指向对象的类型的时候(比如声明变量),我们应该怎么办。
Case1:对于函数的局部变量 例如,有一个函数template<class T> void func (T iter)
,其中T是指向某个特定对象的指针,那么如果在func
中需要指针指向对象类型的变量应该怎么办? 这种情况可以采用模版的参数推导机制。
1 2 3 4 5 6 7 8 9 template <class T>void func (T t) { func_imple (t, *t); }tempalte<class T, class U> void func_impl (T t, U u) { U temp; }
通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型,但是事情往往不那么简单。例如,如果我想把传递给func的这个指针参数所指的类型作为返回值,显然这个方法不能凑效了,这就是我们的case 2。
Case2:对于函数的返回值 比如说我们想这样,我们可以在func_impl
中把U作为返回值,但问题是用户需要调用的是func
来返回这个指针所指向的相应型别。下面的*T
是错误的。
1 2 3 4 5 6 7 8 9 10 11 template <class T > (*T) func (T t) { return func_impl (t, *t); }tempalte<class T, class U> U func_impl (T t, U u) { U temp; .... return temp }
解决方案 可以定义一个iterator
,在定义的时候为期指向的对象类型制定一个别名,如下,而后只要需要其指向的对象的类型,只要直接引用就好了。
注意,关键词typename必须加上,用意在于告诉编译器这是一个型别,否则编译器不知道MyIter<T>::valuetyepe
代表的是一个型别或是member function或者data member。
1 2 3 4 5 6 7 8 9 10 11 12 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 func (I iter) { return *iter; }
等等,还有问题。。。 并不是所有迭代器类型都是class type,原生指针就不是! 如果不是class type,就无法为它定义内嵌型别。 因为func如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过:
1 2 int *p = new int (52 ); cout<<func (p)<<endl;
解决方案:模版偏特化 如果class template拥有一个以上的template参数,我们可以针对其中某个(或数个,或全部)template参数进行特化工作。换句话说,我们可以再泛化设计中提供一个特化版本。 假如有一个class template如下:
1 2 template <typename T>class C { ... };
所谓的partial specialization的意思是:针对任何template参数更进一步的条件设置所涉及出来的一个特化版本。我们可以使上面的class template,使之接受一个形式如下的partial specialization:
1 2 template <typename T>class C <T*> { ... };
这个特化版本仅适用于“T为原生指针”的情况,T为原生指针便是T为任意类型的一个进一步的条件限制。
有了这个模版偏特化的利器,我们可以解决前述“内嵌型别”未能解决的问题。先前的问题是,原生指针并非class,因此无法为它们定义内嵌型别,现在我们可以设计特化版本的迭代器。
所以,我们设计一个专门的class template用来萃取 迭代器的特性
注意,value_type只是迭代器特性之一,而非全部。比如说还有difference_type,reference等
1 2 3 4 tempalte<class I >struct iterator_traits { typedef typename I::value_type value_type; }
意思就是,如果I定义有自己的value_type,那么通过这个traits 的作用,萃取出来的value_type就是I::value_type。也就是说,前面的func()
可以改写成:
1 2 3 4 template <class I >typename iterator_traits<I>::value_type func (I iter) { return *iter; }
我们再为iterator_traits
有一个偏特化版本,这就解决了原生指针不是class type的问题.
1 2 3 4 tempalte<class T >struct iterator_traits <T*> { typedef T value_type; }
还不够! 我们需要声明一个value_type类型的左值,但是却给iterator_traits传递了一个const int,显然结果有问题,于是,为const T 也另起炉灶。
1 2 3 4 template <class T >struct iterator_traits <const T*> { typedef T value_type; }
设计迭代器 常用的迭代器相应型别有五种,traits会很忠实地将原汁原味榨取出来。
1 2 3 4 5 6 7 8 template <class I >struct iterator_traits { typedef typename I::iterator_category iterator_category; typedef typename I::value_type value_type; typedef typename I::difference_type difference_type; typedef typename I::pointer pointer; typedef typename I::reference reference; }
value_type 如上。
difference_type 这用来表示两个迭代器之间的距离,它可以表示一个容器的最大容量,STL的count()
其返回值就必须使用这个diffrence_type。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 template <class I , class T >typename iterator_traits<I>::difference_type count (I first, I last, const T& value) { typename iterator_traits<I>::difference_type n = 0 ; for (; first != last; first++) { if (*first == value) n++; } return n; }template <class I , class T >typename iterator_traits<I>::difference_type count (I first, I last, const T& value) { typename iterator_traits<I>::difference_type n = 0 ; for (; first != last; first++) { if (*first == value) n++; } return n; }template <class I >class iterator_traits <I*> { typedef ptrdiff_t difference_type; }
reference 迭代器分为两种:不允许改变“所指对象的内容”(const int* p),允许改变“所指对象的内容”(int* p)。当我们对一个mutable iterator进行提领操作(解引用)时,不获得的不应该是右值,应该是一个左值,因为右值不能够被赋值操作。
1 2 3 4 5 int * pi = new int (5 );const int * pic = new int (9 ); *pi = 7 ; *pic = 1 ;
这里,reference type指的是迭代器封装对象的类型的引用。这个类型的出现主要是为了解决对指针进行解引用的时候,返回什么样的对象的问题。
当p是一个mutable iterator时,如果value_type是T,那么*p的型别不应该是T,应该是T&
当p是一个constant iterator时,如果value_type是T,那么*p的型别不应该是const T,应该是const T&
pointer_type C++中指针和引用总是有着密切的关系。如果我们想返回迭代器封装的对象的地址,就需要用到这里的pointer_type。 我们把reference_type和pointer_type这两个相应型别分别加入traits:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 template <class T >struct iterator_traits { typedef typename I::pointer pointer; typedef typename I::reference reference; }template <class T > struct iterator_traits <T*> { typedef typename T* pointer; typedef typename T& reference; }template <class T > struct iterator_traits <T*> { typedef typename T* pointer; typedef typename T& reference; }
iterator_category 在STL中,共有以下5种迭代器类型:
Input Iterator: 只读
Output Iterator: 只写
Forward Iterator: 读写
Bidirectional Iterator: 可双向移动
Random Access Iterator: 支持p+n,p-n,p[n],p1-p2,p1<p2
在STL的各种算法中,遍历元素是很常用的,于是我们就用advance()
这个函数作个例子,看看每个迭代器的类型,这个函数负责把迭代器移动特定的长度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 template <class InputIterator , class Distance >void Advance_II (InputIteraotr& i, Distance n) { while (n--) i++; }template <class BidirectionalIterator , class Distance >void Advance_BI (BidirectionalIterator& i, Distance n) { if (n >= 0 ) while (n--) i++; else while (n++) i++; }template <class RandomAccessIterator , class Distance >void Advance_RAI (RandomAccessIterator& i, Distance n) { i += n; }
最后,我们可以构想一个把这3个函数封装起来的函数advance
,专门负责迭代器的移动。
1 2 3 4 5 6 7 8 9 template <class InputIterator , class Distance >void advance (InputIterator& I, Distance n) { if (is_ramdom_access_iterator (i)) advance_RAI (I, i); else if (is_bidirectional_iterator (i)) Advance_BI (I, i); else Advance_II (I, i); }
如果traits有能力萃取出迭代器的种类,我们便可以利用这个“迭代器类型相应型别”作为advance
的第三个参数。这个相应型别一定必须是一个class type,因为编译器需要仰赖它进行重载决议。下面定义的5个class,代表五种迭代器类型:
1 2 3 4 5 6 struct input_iterator_tag { }struct output_iterator_tag { }struct forward_iterator_tag : public input_iterator_tag { }struct bidirectional_iterator_tag : public forward_iterator_tag { }struct random_access_tag : public bidirectional_iterator_tag { }
1 2 3 4 5 template <class InputIterator , class Distance >void advance (InputIterator& i, Distance n) { __advance(i, n, type_traits<i>::iterator_category ()); }
为了满足上述行为,traits必须再增加一个相应的型别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <class I >struct iterator_traits { ... typedef typename I::iterator_category iterator_category; }template <class I >struct iterator_traits <T*> { ... typedef random_access_iterator_tag iterator_category; }template <class I >struct iterator_traits <const T*> { ... typedef random_access_iterator_tag iterator_category; }
总结 设计适当的相应型别,是迭代器的责任,设计适当的迭代器,是容器的责任。唯容器本身,才知道设计什么类型的迭代器访问自己,并执行各种行为。
在STL中,所有的迭代器都遵从上面的设计原则,都要提供上面说过的五种类型,但是,人总会有挂一漏万的时候,为了设计上的方便,STL提供了一个标准的迭代器壳:
1 2 3 4 5 6 7 8 9 10 11 12 template <class Category ,class T ,class Distance = ptrdiff_t class Pointer = T*class Reference = T&>struct iterator { typedef Category iterator_category; typedef T value_type; typedef Distance difference_type; typedef Pointer pointer; typedef Reference reference; };
这样就免去了声明这些类型的麻烦,当你想自定义一个迭代器的时候:
1 2 template <class Item >struct MyIter : public std::iterator<std::forward_iterator_tag, Item> { … }