Traits就像一台”特性萃取机“,它可以毫无偏差的榨取各个迭代器的特性。在讲Traits之前,我们先要把迭代器的特性搞清楚。
什么是迭代器的特性?
迭代器的特性就是迭代器的型别,最常用的迭代器的5中种型别(这5种型别都为内嵌型别),每一种迭代器都有以下五种型的定义:
1.value_type
迭代器所指向对象的类型
2.difference_type
表示两个迭代器之间的距离,可以用来表示一个容器的最大容量。对于连续空间的容器而言,头尾之间的距离就是最大容量。
针对不同的迭代器,都有不同的difference_type,在Traits(迭代器特性萃取机)中有针对原生指针而写的特化版本,它以c++标准库中ptrdiff _t(头文件<stddef.h>或<cstddef>)作为原生指针的difference_type。原则上原生指针也是一种迭代器。
源码:
//原版(未特化版本)
template<class I>// I为一个迭代器类型
struct iterator_traits
{
...
typedef typename I::difference_type difference_type;
};
//针对原生指针 T* 设计的偏特化版本
template<class T>
struct iterator_traits<T*>
{
...
typedef ptrdiff_t difference_type;
};
//针对原生指针 const T* 设计的偏特化版本
template<class T>
struct iterator_traits<const T*>
{
...
typedef ptrdiff_t difference_type;
};
ptrdiff _t:c/c++标准库中定义的一个与机器相关的
数据类型,ptrdiff _t类型变量通常用来保存两个指针加减法操作得到的结果,通常被定义成long int类型。
3.reference__type
迭代器指向对象类型的引用 ,比如我们在实现对operator*的重载时:
Item& operator*()const//Item是迭代器指向对象的类型
{
return *ptr;
}
Item&就是一个reference__type。
4.pointer_type
迭代器指向对象类型的指针,比如在实现operator->()重载时:
Item* operator*()const//Item是迭代器指向对象的类型
{
return ptr;
}
Item*就是一个pointer_type
在Traits中原生指针和const原生指针的偏特化版本中同样对pointer_type和reference__type做出了自己的定义。
//原版(未特化版本)
template<class I>// I为一个迭代器类型
struct iterator_traits
{
...
typedef typename I::reference__type reference__type;
typedef typename I::pointer_type pointer_type;
};
//针对原生指针 T* 设计的偏特化版本
template<class T>
struct iterator_traits<T*>
{
...
typedef T* reference;
typedef T* pointer;
};
//针对原生指针 const T* 设计的偏特化版本
template<class T>
struct iterator_traits<const T*>
{
...
typedef const T* reference;
typedef const T* pointer;
};
5.iterator_category
这个型别我们可以认为是迭代器的类型。总的来说,迭代器可分为5种类型:
①InputIterator 只读;
②OutputIterator 只写;
③ForwardIterator 可读写,但是只能前向移动;
④BidrectionalIterator 可读写,可双向移动;
⑤RandomAccessIterator 这个迭代器涵盖以上4种的所有能力再加上p+n,p-n,p[n],p1-p2,p1<p2;
这些迭代器的分类和从属关系可以用下图表示(下图只是概念和强化的关系,并不是C++中的继承关系):
从上图可以看出,假如你需要一个ForwardIterator,我把一个RandomAccessIterator给你,也是可以用的,每一个RandomAccessIterator 都是一个ForwardIterator 。但是这并不是最佳的选择。要知道,STL是非常注重代码的效率的,我们要严记这一点。所以我们需要iterator_category这个型别来标明迭代器的类型,以供我们使用。
每种迭代器都会有以上5中类型的定义,以下为stl_iterator.h中的实现:
template <class T, class Distance> struct input_iterator {
typedef input_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
struct output_iterator {
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
};
template <class T, class Distance> struct forward_iterator {
typedef forward_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
template <class T, class Distance> struct bidirectional_iterator {
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
template <class T, class Distance> struct random_access_iterator {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef T* pointer;
typedef T& reference;
};
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;
};
迭代器Triats编程,简单来说利用内嵌型别和template模板参数的自动推导能力,萃取出不同迭代器不同的特性(内嵌型别)。
为什么需要Traits?
很简单,解决效率问题。在上文中我提到过,用一个RandomAccessIterator替代一个ForwardIterator是一种很不明智的做法。大大降低了STL的效率。再来一个更明显的例子。
Advance:假如我们现在需要使一个迭代器it向后移动n歩,但是我们并不知道迭代器的类型。我们应该怎么做?
采用循环加法?
while (n>0)
{
it++;
n--;
}
如果it是上面所讲的前3中迭代器的任何一种,这种方法都是唯一的选择。但是如果it是RandomAccessIterator类型,我们本来采用O(1)的算法直接令it+n就可以达到目的,为什么还要使用O(N)的算法呢?
更可怕的是,如果it是一个双向迭代器BidrectionalIterator,并且n<0,那么这个循环根本就不会进行。it也就不会移动。
所以在一些情况下我们必须知道迭代器的类型。所以就需要Traits。
Traits是如何实现的?
首先我们要给五种迭代器类型定义一个标签
源码:
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_iterator_tag : public bidirectional_iterator_tag {};
在上文介绍类型的时候我说过,迭代器这5中类型并不是c++中的继承关系,那为什么这里我们用到了继承呢?这里就是STL的一个巧妙设计,我们放到后文中讲。
实现:以advance函数为例
函数定义:
template<class InputIterator,class Distance>
inline void advance(InputIterator& i,Distance n )
作用:令迭代器 i 向前移动n歩。
我们知道,对于不同类型的迭代器,我们都有各自不同的移歩操作,那么我是不是从新设计一个__advance,将迭代器的类型作为参数传入,令advance递归调用__advance就可以了呢?移步操作交给__advance来做。如下:
template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)
{
__advance(i, n, 迭代器类型);
}
STL就是这么做的,如下:
template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)
{
__advance(i, n, iterator_traits<InputIterator>::iterator_category);
}
不知道你有没有发现,这个实现的第一个模板参数就是我们的第一个迭代器的类型只读迭代器
InputIterator,这不符合我们编程时的习惯的命名规范,其实不是,这是
STL算法的一个命名规则:以算法所能接受的最低阶的迭代器类型,来为其迭代器型别参数命名。
回头来说,STL是通过一个iterator_traits来将迭代器的类型以参数传入的。iterator_traits是个什么呢?
源码定义:
template <class Iterator>
struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
};
(
typename的作用:Iterator是一个模板类型参数(template),在它被编译器具现化之前,编译器对Iterator一无所知,换句话说:编译器此时并不知道
Iterator::iterator_category是什么,关键词typename的用意在于告诉编译器这是一个型别,这样才能顺利通过编译)
我们看到,iterator_traits就是一个struct定义的一个
模板结构体类类型,模板参数
Iterator
为一个迭代器类型。在iterator_traits内部,它将迭代器的五种内嵌型别全部重定义了一番。就是当我们访问iterator_traits的五种型别时,就是访问对应模板参数Iterator的五种型别。五种迭代器的型别实现看上文代码。原生指针T*和const原生指针const T*也算是两种迭代器,我们要对这两个指针进行特殊的
偏特化处理:
/
//这两个指针我们认为是RandomAccessIterator类型
//
//T*
template <class T>
struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
//
//const T*
template <class T>
struct iterator_traits<const T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
得到迭代器的类型后我们就可以利用函数重载实现不同版本的__advance就可以了。这时就用到我们上面说的继承关系了。
我们先看一个例子:
#include<iostream>
using namespace std;
struct A{};
struct B :public A{};
struct C :public B{};
template<class T>
void show(T& p,A)
{
cout << "A" << endl;
}
int main()
{
int *p;
show(p,A());
show(p,B());
show(p,C());
system("pause");
return 0;
}
代码的执行结果如图所示:
我们可以看到,虽然我们并没有重载实现B,C的show函数,但是因为B继承自A,它自动“传递调用”了A的show函数。他正好符合我们将要写的__advance的重载。我们正好可以将A,B对应到我们的迭代器类型。
(OutputIterator只写,不能移动)
所以__advance重载函数我们一共有以下三个版本:
template<class InputIterator,class Distance>
inline void __advancce(InputIterator &i, Distance, InputIteratorTag)
{
while (n--)
{
++i;
}
}
template<class BidirectionIterator,class Distance>
inline void __advance(BidirectionIterator &i, Distance n, BidirectionalIteratorTag)
{
if (n >= 0)
{
while (n--)//如果是前置--就会少走一步
{
++i;
}
}
else
{
while (n++)
{
--i;
}
}
}
template<class RandomAccessIterator,class Disance>
inline void __advance(RandomAccessIterator& i, Distance n, RandomAccessIteratorTag)
{
i += n;
}
当我们传入的模板参数是ForwardIterator时,它会自己调用InputIterator的重载函数。
advance的Traits调用过程图:
同理,迭代器的Distance函数(求两个迭代器之间的距离)也是如此实现。下面是实现代码,大家可以自己推导一下:
根据继承属性,__distance实现两个版本就可以了,为什么?
template<class InputIterator>
inline typename IteratorTraits<InputIterator>::DifferenceType
void Distance(InputIterator first, InputIterator last)
{
return __Distance(first, last, IteratorTraits<InputIterator>::IteratorCategory());
}
template<class RandomAccessIterator>
inline typename IteratorTraits<RandomAccessIterator>::DifferenceType
void __Distance(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIteratorTag)
{
return last - first;
}
template<class InputIterator>
inline typename IteratorTraits<InputIterator>::DifferenceType
void __Distance(InputIterator first, InputIterator last, InputIteratorTag)
{
int n = 0;
while (first != last)
{
++first;
}
return n;
}
总结
设计什么样的型别时迭代器的责任,设计什么样的迭代器是容器的责任。只有容器本身才知道该设计什么样的迭代器来维护自己。Traits大量用于STL实现品中,它里用了内嵌型别和编译器的模板template参数推导功能,增强了C++关于型别认证方面的能力,弥补了C++不为强型别语言的遗憾。Traits编程使我们必不可少的一门编程技巧。