STL 系列 —— 迭代器与 traits 编程(二)

系列文章

STL 系列 —— 空间配置器(一)
STL 系列 —— 迭代器与 traits 编程(二)

前言

计算机科学的许多问题,都是通过增加间接层的方式解决的,STL 最为人称道的是将容器和算法的实现分割开了,迭代器就是分开它们必须的中间层。算法是容器的使用者,如若二者不独立开来,算法实现的过程中会暴露很多关于容器使用的细节,而这些细节并不是每个人都可以了解的,何不让相关容器的实现者来对这些实现细节进行封装,使用者只管使用就好了,这样算法也可以更好地进行泛化了。

迭代器内置类型声明

迭代器种类 iterator_category

迭代器可以看作是一种指针,它们是用来指向其他对象的一种对象,迭代器最重要的工作就是对 operator*operator-> 进行重载(overloading),例如 find 算法:

template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value) {
  while (first != last && *first != value) ++first;
  return first;
}

通过作用在 iterator 指向的对象上,在 find 算法中,我们仅仅要求迭代器能够以某种线性顺序遍历某个数据结构,以访问其中所有的元素。

但是这仅仅是众多泛型算法的一个例子,其他的泛型算法可能对迭代器的要求又是其他,因此,针对这些算法需要的不同要求,针对移动特性和施行的操作,可以将迭代器分成 5 类:

  • input iterator: 对应数据只读的情况,所指对象不能被外界改变
  • output iterator: 数据只写
  • forward iterator:允许读写,但是只能单方向依次递增,不支持 operator-- 操作
  • bidirectional iterator:允许读写,可以向前/向后依次递增/递减,支持逆向访问数据
  • random access iterator:涵盖了所有指针的算术能力,p+n,p-n,p1-p2,p1 < p2 等

他们之间的继承关系如下图所示:
在这里插入图片描述
对应代码:

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 {};

这里它们是一个空的类相互继承,使用它们也不会有额外的负担。

针对不同的迭代器种类所支持的操作类型,算法可以选择最适合的版本来进行实现,可以支持 p+n 操作的就不用再 p++ n 次了。

以 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;
}

并且同一个算法针对不同的迭代器类型,实现多个版本之后,如果使用 if else 来判断到底使用哪一个版本,只有运行期才能决定,这样效率很慢,为了能够编译期就可以确定,STL 使用了重载函数的机制来自动推导。这里是利用 C++ 的继承与多态,若没有精准匹配的类型,子类可以匹配到父类上,这也就是为什么 __advance 没有 forward_iterator_tag 类型的版本,因为找不到,会自动匹配到 input_iterator_tag 的实现版本上,秒啊!

至于如何获得输入对象的对应迭代器类型,就要引出下面的重要话题,traits 了。

数值型别 value type

value type 指的就是 iterator 指向的对象类型,假设有一个 iterator type,现在我们需要声明一个变量,其类型为 I 的 value type,我们可以通过转递函数来实现:

template <class I, class T>
void f_impl(I iter, T t) {
 T tmp;  // T 是 I 的 value type
}

template <class T> 
inline void f(I iter) {
  f_impl(iter, *iter);
}

但是上述使用模板推导的方法,无法用来推导然后声明函数自身的返回类型。第二种方法就是要在 iterator 类内声明它的 value type,C++ 允许嵌套式声明,但是当 iterator 是指针不是类的时候就没办法嵌套声明了,这时中间层的作用再一次展现了,我们可以定义一个辅助用的 class,iterator_traits:

template <class Iterator>
struct iterator_traits {
  typedef typename Iterator::value_type    value_type;
};

这样一来我们就可以使用下面的写法来取得 Iterator 的 value type 了:

typename iterator_traits<I>::value_type

int* 它的 value type 应当是 int,上述根据模版推导的规则,value type 应该是 int*,因此针对指针类型可以采用模版偏特化,实现对应的指针版本及常量指针的处理:

template <class T>
struct iterator_traits<T*> {
  typedef T  value_type;
};

template <class T>
struct iterator_traits<const T*> {
  typedef T  value_type;
};

由此我们可以通过 iterator_traits 辅助类,可以实现对对象 value type 的获取。当你定义新的类时,如果要与 STL 兼容,类中也应该声明对应的 value type 类型声明。

差距类型 difference type

当迭代器是 random access iterator 时支持指针算术计算,p2-p1 表示两者之间的距离,如果我们需要用到这个距离,那么我们就必须直到它的类型是什么? 如果 I 是指针,则指针相减的类型是 ptrdiff_t(C/C++ 规定的类型,就是带符号的 int 或 long),但是当范围太大,以至于超出了 ptrdiff_t 的表示范围呢?

这时我们需要定义关于迭代器的差距类型 difference type, 以它来作为此类函数的返回类型,使其随着真是情况可以变化,这个类型在某些算法中是会用到的,例如 count,它计算某个值 x 在迭代器范围 [first, end) 中出现的次数,其返回值可能就有 [first, end) 这个范围这么大,所以它的返回值类型就是 difference_type,相应的其也使用类的嵌套声明,声明在 iterator_traits 辅助类中。

template <class T>
struct iterator_traits<T*> {
  typedef typename Iterator::difference_type     difference_type;
};

template <class T>
struct iterator_traits<T*> {
  typedef ptrdiff_t  difference_type;
};

template <class T>
struct iterator_traits<const T*> {
  typedef ptrdiff_t  difference_type;
};
reference type 和 pointer type

当迭代器 p 是具有可读写性质的,当它指向某个类型为 T 的某个对象时,*p 不能返回类型为 T 的对象,它必须返回一个左值,C++ 可以借助返回一个 reference 来返回一个左值,通过左值来进行读写,如果 p 是可修改的,则其 value type 为 T,*p 则应该返回 T&,若 p 是不可修改的迭代器,则 *p 为 const T&,所以一般而言 *p 并不是 p 的 value type,而是它的 reference type。

与指针 operator* 操作相对应的就是 operator->,其应当返回 T* 类型,由此来达到对指向对象的相关操作。

迭代器

这时我们将上述内容汇总,我们了解到,迭代器定义了 5 种内置类型来满足其在不同算法中可能用到的信息,并且通过辅助类 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;
};

// 针对原生指针的特化版本
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 指针的特化版本
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;
};

当你需要定义自己的 iterator 的类时,也需要包含以上 5 种嵌套类型,为了避免类型缺失, STL 提供了一个基类供继承。

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;
};

因此,相对应的,再看 STL 对 5 种迭代器的定义就不难理解了:

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;
};

// reference 和 pointer 均为空类型,所以实现了只写
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;
};

type traits

以上基本把关于迭代器和辅助类 iterator traits 的内容说完了, 在 STL 中,traits就是 类模版参数推导 + typepdef 内嵌类型声明 的技巧.

对应的 iterator traits 是用来提取迭代器的特性的,__type_traits 则是负责提取类型(type)特性的,这个类型特性主要是前面提到的,当前对象是否有 trivial 的构造/析构函数,因为这个对算法的选择是效率有很大的影响。了解了 traits 的技巧,就可以很轻松的了解:

struct __true_type {};
struct __false_type {};

template <class type>
struct __type_traits { 
   typedef __true_type     this_dummy_member_must_be_first;
   typedef __false_type    has_trivial_default_constructor;
   typedef __false_type    has_trivial_copy_constructor;
   typedef __false_type    has_trivial_assignment_operator;
   typedef __false_type    has_trivial_destructor;
   typedef __false_type    is_POD_type;
};

值得一提的是,对于是否有 trivial 类型的构造/析构函数,结果应当是 bool 类型,true / false,并且 __type_traits 是需要用在参数推导的情况下,只有 class 的对象可以进行参数推导,因此 STL 定义了两个空的 class,__true_type 和 __false_type,没有任何成员,不会带来额外的负担,又能够表示真假,参与参数推导,秒啊!!

是用示例就是在 STL 系列 —— 空间配置器(一)提到的,空间析构会判断其是否是 POD 类型,然后采用不同的析构策略,具体源码如下:

// 逐个析构,注意调用这里的前提是,最后一个输入为 __false_type,
// 表示对象的析构函数是重要的
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);
}

// 如果范围对象的析构函数是无关痛痒的,则什么都不做
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
  __destroy_aux(first, last, trivial_destructor());
}

一切都串起来了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值