Item 47: 请使用traits class表现类型信息

为了说明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()
      );    //神奇而美妙的代码
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值