为了说明traits class的作用,这一节用迭代器做了一个例子。
STL有五种迭代器:
(1)Input迭代器只能向前移动,且一次一步,客户只可读取它们所指的东西,而且只能读一次;这一类代表是istream_iterator。
(2)Output迭代器跟Input相似,但是客户只可涂写它们所指的东西,而且只能写一次;这一类代表是ostream_iterator。
(3)Forward迭代器可以做前述两种分类所能做的每一件事,而且可以读或写其所指物一次以上。
(4)Bidirectional迭代器除了可以向前移动,还可以向后移动,例如list,set,multiset,map,multimap迭代器。
(5)最厉害的是random access迭代器,它可以完成上面各分类迭代器所能做的每一件事情,而且可以向前或向后跳跃任意距离。vector, deque,和string提供的迭代器都属这一类。
对于这五种分类,C++标准程序库分别提供专属的卷标结构加以确认:
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{};
他们之间的继承关系都是有效地is-a关系
假设我们欲实现一个函数advance,用来将某个迭代器移动某个给定距离,那么我们必须先判断该iterator是否为random access的,否则要采用不同的跳跃方法:
template<typename IterT, typename DistT>
void advance( IterT& iter, DistT d ){
if( iter is a random access iterator )
iter += d;
else{
if( d >= 0 ){ while(d--) ++iter; }
else{ while( d++ ) --iter; }
}
}
怎样才能得知iter类型的某些信息呢?traits允许我们在编译期间取得某些类型信息(这样是不是就能实现RTTI了?)。
traits并不是C++关键字或一个预先定义好的构件:它们是一种技术,也是一个C++程序员共同遵守的协议。这个技术的要求之一是,它对内置类型和用户自定义类型的表现必须一样好,因此类型的traits信息必须位于类型自身之外。标准技术是把它放进一个template及其一个或多个特化版本中。这样的template在标准程序中有若干个,其中针对迭代器者被命名为iterator_traits。template<typename IterT>
struct iterator_traits;
iterator_traits的动作方式是:针对每一个类型IterT,在struct iterator_traits内声明某个typedef名为iterator_category,这个typedef用来确认IterT的迭代器分类。
iterator_traits以两个部分完成这个任务。首先它要求每一个“用户自定义迭代器类型”必须嵌套一typedef,名为iterator_category,用来确认适当的卷标结构。如下:代码中关于typename的使用,可参考Item 42 有详细解释
template<...>
class deque{
public:
class iterator{
public:
typedef random_access_iterator_tag iterator_category;
...
}
...
};
//list的迭代器可双向行进
template<...>
class list{
public:
class iterator{
public:
typedef bidirectional_iterator_tag iterator_category;
...
}
...
};
//iterator_traits将鹦鹉学舌般地响应iterator class的嵌套式typedef。
//类型IterT的iterator_category用来表现“IterT说它自己是什么”
template<typename IterT>
struct iterator_traits{
typedef typename IterT::iterator_category iterator_category;
...
};
//为了支持指针迭代器,iterator_traits特别针对指针类型提供一个偏特化版本
template<typename IterT>
struct iterator_traits<IterT*>{
typename random_access_iterator_tag iterator_category;
...
};
现在我们可以用iterator_traits来判定迭代器的类型了:
template<typename IterT, typename DistT>
void advance( IterT& iter, DistT d ){
if( typeid(typename std::iterator_traits<IterT>::iterator_category)
== typeid(std::random_access_iterator_tag)){
iter += d;
}
else
...
}
这段代码有两个问题,其一是它无法通过编译,详情请看Item 48;其二是,我们知道,IterT类型在编译期间获知,因此iterator_traits::iterator_category也可以在编译期间确定,但是if语句却是在运行期才会核定,为什么将可在编译期完成的事延到运行期才做呢?这不仅浪费时间,也造成可执行文件膨胀。
如何让编译器在编译时间就对类型进行核定呢?重载
template<typename IterT, typename DistT>
void doAdvance( IterT& iter, DistT d, std::random_access_iterator_tag ){
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance( IterT& iter, DistT d, std::bidirectional_iterator_tag){
if( d>=0 ) { while (d--) ++iter; }
else { while( d++ ) --iter; }
}
template<typename IterT, typename DistT>
void doAdvance( IterT& iter, DistT d, std::input_iterator_tag){
if( d<0 )
throw std::out_of_range("Nagative Distance");
while (d--) ++iter;
}
template<typename IterT, typename DistT>
void advance( IterT& iter, DistT d ){
doAdvance( iter, d,
typename std::iterator_traits<IterT>::iterator_category()
); //神奇而美妙的代码
}