STL的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。容器和算法的泛型化,用class templates和function templates就可以分别达成目标。
迭代器是一种智能指针。
为了获得迭代器所指对象的型别,一种是利用了function template的参数推导机制。例子如下:
template <class I,class T>
void func_impl(I iter,T t)
{
T tmp; //T就是迭代器所指之物的型别
//。。。完成func()应该做的全部工作。
}
template <class I>
void func(I iter)
{
func_impl(iter,*iter); //func的工作全部移往func_impl
}
int main()
{
int i=100;
func(&i);
}
我们以func()为对外接口,却把实际操作全部置于func_impl()之中,由于func_impl是一个function template,一旦被调用,编译器就会自动进行template参数推导,导出型别T,解决了问题。
Traits编程技法
上述的参数型别推导技巧虽然可以用于value type,却非全面可用:万一value type必须用于函数的传回值,就束手无策了。因为函数的template参数推导机制推导的只是参数,无法推导函数的返回值型别。
Traits技法采用内嵌型别来实现获取迭代器型别这一功能需求,其意义是,如果I定义有自己的value type,那么通过这个traits的作用,萃取出来的value_type就是I::value_type。最常用的五种迭代器型别:
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:表示两个迭代器之间的距离。
- reference type
- pointer type
- iterator_category。为下面的函数重载做准备。
根据移动特性与施行操作,迭代器被分为五类:
- Input Iterator:只读
- Output Iterator:唯写
- Forward Iterator:允许“写入型”算法在此种迭代器所形成的区间上进行读写操作。
- Bidirectional Iterator:可双向移动。
- Random Access Iterator:前四种迭代器都只供应一部分指针算术能力,第五种则涵盖所有指针算数能力。
设计算法时,尽量针对某种迭代器提供一个明确定义,并针对更强化的某种迭代器提供另一种定义,这样才能在不同情况下提供最大的效率。
以advanced()为例
template <class InputIterator,class Distance>
void advance_II(InputIterator& 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;
}
在程序调用advance()时,如果选择advance_II(),对于RandomAccessIterator来说很没效率(O(1)变O(n)),如果用advance_RAI(),无法接受InputIterator,现在我们要将三者合一,如果采用:
template <class InputIterator,class Distance>
void advance(InputIterator& i,Distance n)
{
if(is_random_access_iterator(i))
advance_RAI(i,n);
else if(is_bidirectional_iterator(i))
advance_BI(i,n);
else
advance_II(i,n);
}
这样是在执行期间才决定使用哪一个版本,会影响效率。最好在编译期就选择了正确的版本,是不是想起了重载函数呀。
我们先定义五个class,表示五种迭代器类型,作为标记使用。
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 {};
用继承,可以不必再写"单纯只做传递调用"的函数,比如forward_iterator版本的advance(),还是调用的input_iterator版本,不用再写一个advance_FI版本。
现在我们来写重载函数__advance()(由于只在内部使用,所以函数名称加上特定的前导符)
template <class InputIterator,class Distance>
inline void __advance(InputIterator& i,Distance n,input_iterator_tag)
{
//单向,逐一前进
while(n--)
++i;
}
template <class BidirectionalIterator,class Distance>
inline void __advance(BidirectionalIterator& i,Distance n,bidirectional_iterator_tag)
{
//双向,逐一前进
if(n>=0)
while(n--)
++i;
else
while(n++)
--i;
}
template <class RandomAccessIterator,class Distance>
inline void __advance(RandomAccessIterator& i,Distance n,random_access_iterator_tag)
{
//双向,跳跃前进
i+=n;
}
使用的时候用
template <class InputIterator,Distance n>
inline void advance(InputIterator& i,Distance n)
{
__advance(i,n,iterator_traits<InputIterator>::iterator_category());
}
//iterator_traits<InputIterator>::iterator_category()将产生一个临时对象,隶属于前述四个迭代器//类型之一,然后编译器决定如何重载。
//template <class InputIterator,Distance n>中的InputIterator是因为STL的命名规则:
//以算法所能接受之最低阶迭代器类型,来为其迭代器型别参数命名。
总结
设计适当的迭代器是容器的责任,只有容器才知道设计怎样的迭代器来遍历自己,并执行迭代器该有的各种行为。
traits编程技法大量运用于STL实现品中。它利用“内嵌型别”的编程技巧与编译器的template参数推导功能,增强C++未能提供的关于型别认证方面的能力。
SGI STL的私房菜:__type_traits,以后再更新吧,很有趣的一个技术。