STL源码剖析——iterators与trait编程#3 iterator_category

  最后一个迭代器的相应类型就是iterator_category,就是迭代器本身的类型,根据移动特性与实行的操作,迭代器被分为了五类:

  • Input Iterator:这种迭代器所指的对象,不允许外界改变。只读(read only)。
  • Output Iterator:唯写(write only)
  • Forward Iterator:允许写入型算法在此种迭代器所形成的区间上进行读写操作,具有Input迭代器的全部功能和Output迭代器的大部分功能。支持++运算符。
  • Bidirectional Iterator:可双向移动。某些算法需要逆向遍历某个迭代器区间,可以使用Bidirectional Iterator。在Forward的基础上支持--运算符。
  • Random Access Iterator:前四种迭代器都只供应一部分指针算术能力(operator++、operator--),而这种则涵盖所有指针算数能力,包括p+n、p-n、p[n]、p1-p2、p1<p2

  这些迭代器的分类与从属关系,下图可以表示,直线与箭头代表的并非C++的继承关系,而是concept(概念)与refinement(强化)的关系。

  设计算符时,如果可能,我们尽量针对图中的某种迭代器提供一个明确的定义,并针对更强化的某种迭代器提供另一种定义,这样才能在不同情况下提供最大效率。假设有个算法可接受Forward Iterator,你以Random Access Iterator喂给它,它当然也接受,因为一个Random Access Iterator一定是一个Forward Iteartor。但是可用并不是代表最佳。

  例如最近我刚好看到copy()函数,它就为不同的迭代器定义了不同的复制方法,它提供了Input Iterator的特化版本和针对更强化的Random Access Iteartor的特化版本,为什么如此?因为于非随机迭代器而言,它们无法做到迭代器之间的减法处理(operator-()),所以每次循环判断是否达到被复制区间的尾部时,只能与last(区间为[first, last) )迭代器进行比较,这个就是Input Iterator的特化版本;而针对更强化的随机迭代器而言,因为其提供的减法处理,所以可以利用difference type记录区间大小(last - first),然后在复制循环中只用判断循环次数是否超过区间大小就好了,这个就是Random Access Iterator的特化版本,相对而言后者的速度显然比前者快很多,这就是在不同情况下提供最大效率。

  但重点是,我们必须有能力去萃取出迭代器的种类,才能调用到相应的特化版本。这最后一个迭代器的相应类型一定必须是一个类(class type),不能只是数值号码之类的东西,因为编译器需要仰赖它来进行重载判断。下面定义五个classes,代表五种迭代器类型:

1 struct input_iterator_tag {};
2 struct output_iterator_tag {};
3 struct forward_iterator_tag :public input_iterator {};
4 struct bidirectional_iterator_tag :public forward_iterator_tag {};
5 struct random_access_iterator_tag :public bidirectional_iterator_tag {};

  这些classes只作为标记用,所以不需要任何成员。至于为什么运用继承机制,它可以促成重载机制的成功运作,另一个好处是,通过继承,我们可以不必写“单纯只做传递调用”的函数,我们可以做一个小测试:

 1 struct B {};            //B 可比拟为Input Iterator
 2 struct D1 :public B {}; //D1可比拟为Forward Iterator
 3 struct D2 :public D1 {};//D2可比拟为Bidirectional Iterator
 4
 5 template<typename I>
 6 func(I& p, B)
 7 {
 8     cout << "B version" << endl;
 9 }
10 template<typename I>
11 func(I& p, D2)
12 {
13     cout << "D2 version" << endl;
14 }
15 int main()
16 {
17     int *p;
18     func(p, B());    //输出“B version”
19     func(p, D1());    //输出“B version”
20     func(p, D2());    //输出“D2 version”
21 }

  何为“单纯只做传递调用”的函数,如果Forward Iteartor的特化版本的功能Input Iterator也能做到,那在非继承情况下,它是如下的,以advance函数的内部函数__advance()为例,该函数具有跳到容器内某指定位置的功能:

 1 //Distance即为两迭代器距离类型(difference type)
 2 template<typename InputIterator, typename Distance>
 3 inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
 4 {
 5     //单向,逐一前进
 6     while (n--)++i;
 7 }
 8
 9 template<typename InputIterator, typename Distance>
10 inline void __advance(InputIterator& i, Distance n, forward_iterator_tag)
11 {
12     __advance(i, n, input_iterator_tag());    //单纯的转调用
13 }

  如果是继承的情况下,只需写一个Input Iteartor版本即可,而无需再写一个Forward Iteartor特化版本然后做传递调用处理。而因处理方式不一,它还提供了Bidirectional Iteartor的特化版本和Random Access Iteartor的特化版本,这里不再赘述,但有必要提一下其上层对外开放接口advance函数,这一上层接口只需两个参数,当它准备将工作转给上述的__advance()时,才自行加上第三参数:迭代器类型。因此,这个上层函数必须有能力从它所获得的迭代器推导出其类型——这份工作自然是交给traits机制:

1 template<typename InputIterator, typename Distance>
2 inline void advance(InputIterator& i, Distance n)
3 {
4     __advance(i, n,
5         iterator_traits<InputIterator>::iteartor_category());
6 }

  iterator_traits<InputIterator>::iteartor_category()将产生一个临时对象,其类型应该隶属于前述四个迭代器类型(I、F、B、R)之一。然后,根据这个类型,编译器才决定调用哪一个__advance()重载函数。

  因此,为了满足上述行为,traits必须在增加一个相应类型:

 1 template<typename I>
 2 struct iterator_traits
 3 {
 4     ...
 5     typedef typename I::iterator_category iterator_category;
 6 };
 7 template<typename T>
 8 struct iterator_traits<T*>
 9 {
10     ...
11     //原生指针是一种Random Access Iterator
12     typedef typename random_access_iterator_tag iterator_category;
13 };
14 struct iterator_traits<const T*>
15 {
16     ...
17     //原生pointer-to-const是一种Random Access Iterator
18     typedef typename random_access_iterator_tag iterator_category;
19 };

  一个迭代器的类型,应该是各种迭代器类型中最强的那个,例如int*,既是Random Access Iterator,又是Bidirectional Iterator,同时也是Forward Iterator,而且也是Input Iteartor,那么,其类型应该归属为randm_access_iterator_tag。

  另外还需注意的是STL算法的一个命名规则,如advance(),其迭代器类型参数命名为InputIteartor,这表示只要其迭代器类型的基类为input_iteartor_tag,该函数就能接受,即接受各种类型的迭代器。以算法所能接受之最低阶迭代器类型,来为其迭代器类型参数命名。

std::iterator的保证

  为了符合规范,任何迭代器都应该提供相应类型,否则便是有别于整个STL整个架构,可能无法与其他STL组件顺利搭配。因此STL提供了一个iteartor class,如果每个新设计的迭代器都继承自它,就可保证符合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;
};

  iteartor class不含任何成员,纯粹只是类型定义,所以继承它并不会带来任何额外的负担。由于后三个参数皆有默认值,故新的迭代器只需提供前两个参数即可。如我们第一节土法炼钢的ListIter,如果改用正式规格,应该这么写:

1 template<typename Item>
2 struct ListIter: public std::iterator<std::forward_iterator_tag, Item>
3 {...}

  设计适当的相应类型,是迭代器的责任。设计适当的迭代器,则是容器的责任。唯容器本身,才知道设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为。至于算法,完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就行。

原文地址:https://www.cnblogs.com/MisakiJH/p/11678500.html

时间: 2024-10-07 09:30:36

STL源码剖析——iterators与trait编程#3 iterator_category的相关文章

STL源码剖析——iterators与trait编程#2 Traits编程技法

在算法中运用迭代器时,很可能用到其相应类型.什么是相应类型?迭代器所指对象的类型便是其中一个.我曾有一个错误的理解,那就是认为相应类型就是迭代器所指对象的类型,其实不然,相应类型是一个大的类别,迭代器所指对象的类型只是里面的其中一个.后面会讨论到相应类型的另外几种. 假设算法需要声明一个变量,以“迭代器所指对象的类型”为类型,该怎么做?或许我们可能会想到RTTI性质中的typeid(),但获得的只是类型名称,并不能拿来声明变量. 其中一个解决方法是:利用模版函数中的参数推导(argument d

STL源码剖析——iterators与trait编程#4 iterator源码

在前两节介绍了迭代器的五个相应类型,并讲述如何利用traits机制提取迭代器的类型,但始终是把iteartor_traits类分割开来讨论,这影响我们的理解,本节将给出iteator的部分源码,里面涵盖了整个iteartor_traits泛化版本.偏特化版本以及一些算法的完整代码.重新把先前讲的知识捋顺一下. 1 //节选自SGI STL<stl_iterator.h> 2 3 //五种迭代器类型,继承表示,越底层的基类越低级,越上层的子类越高级 4 struct input_iterator

STL源码剖析——iterators与trait编程#1 尝试设计一个迭代器

STL的中心思想在于:将数据容器与算法分开,独立设计,再用一帖粘着剂将它们撮合在一起.而扮演粘着剂这个角色的就是迭代器.容器和算法泛型化,从技术角度来看并不困难,C++的模板类和模板函数可分别达成目标,但如何设计出两者之间良好的粘着剂,才是大难题. 我们可以来尝试一下自己设计一个迭代器,看途中会遇到一些什么样的难题,同时看看SGI STL是怎么解决它们的.不过在此之前,我们可以先使用一下迭代器,看看其有什么样的功能,以find()函数为例: 1 //来自SGI <stl_algo.h> 2 t

STL源码剖析——Iterators与Traits编程#5 __type_traits

上节给出了iterator_traits以及用到traits机制的部分函数的完整代码,可以看到traits机制能够提取迭代器的特性从而调用不同的函数,实现效率的最大化.显然这么好的机制不应该仅局限于在STL里面使用,在前某一节中我们也有说到,traits机制能够萃取类的特性,而这个类分为两个类别,一是迭代器类,二普通类,迭代器类我们已经有所了解了,那么本节就来学习负责萃取普通类型特性的__type_traits吧. 于普通类型而言,我们所关注的特性是指:这个类型是否具备non-trivial d

STL源码剖析—迭代器与traits编程方法

STL的中心思想就是将算法和容器分开,彼此独立设计,最后再以粘合在一起,算法和容器的泛型化,并不是很难,C++的class templates和function templates可以达成目标,但是粘合在一起就是迭代器的事情. 这么一说迭代器就是为了粘合算法和容器的,如果单独设计迭代器,那么这个迭代器就必须知道某个特定容器的内部数据,会暴露太多的信息,这样就规定每一种STL容器都提供有专属迭代器. 迭代器所指对象的型别,称为该迭代器的value_type.所谓value_type,是指迭代器所指

stl源码剖析学习笔记(二)traits编程技法简明例程

解释说明 traits侯捷老师的翻译是萃取.其目的就是在编译期进行模板调用的类型识别,从而做一些事情. 最突出的例子,我觉得不是<STL源码剖析>中"迭代器概念与traits编程技法"这一章的说明,而是stl算法中copy的实现.代码在stl源码的stl_algobase.h中. copy的最终实现,大致分为两类,一类是直接整块内存的memmove操作,另一类是一个个对象赋值.其中涉及has_trivial_assignment_operator的类型推断. 如果has_t

《STL源码剖析》学习之traits编程

侯捷老师在<STL源码剖析>中说到:了解traits编程技术,就像获得“芝麻开门”的口诀一样,从此得以一窥STL源码的奥秘.如此一说,其重要性就不言而喻了.      之前已经介绍过迭代器,知道了不同的数据结构都有自己专属的迭代器,不同的迭代器也有不同的特性,由于算法的接口是统一的,通过迭代器的不同属性,算法自动选择正确的执行流程,在完全任务的同时,也尽可能提高算法的执行效率.那算法如何获知迭代器的属性呢?这一光荣的任务就是traits完成的.在STL实现中,traits编程技术得到大量的运用

[转载]《STL源码剖析》阅读笔记之 迭代器及traits编程技法

本文从三方面总结迭代器   迭代器的思想   迭代器相应型别及traits思想   __type_traits思想 一 迭代器思想 迭代器的主要思想源于迭代器模式,其定义如下:提供一种方法,使之能够依序巡防某个聚合物(容 器)所含的元素,而又无需暴露该聚合物的内部表达式.可见她的主要作用便是能够降低耦合,提高代码的 模块性. STL的的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一贴胶着剂将它们撮合 在一起,这贴胶着剂便是迭代器.迭代器的行为类似智能指针(例如标准库的auto_pt

《STL源码剖析》---stl_iterator.h阅读笔记

STL设计的中心思想是将容器(container)和算法(algorithm)分开,迭代器是容器(container)和算法(algorithm)之间的桥梁. 迭代器可以如下定义:提供一种方法,能够依序寻访某个容器内的所有元素,而又无需暴露该容器的内部表达方式. 在阅读代码之前,要先了解一个新概念:Traits编程技法 template <class T> struct MyIter { typedef T value_type //内嵌型别声明 T *ptr; MyIter(T *p = 0