最后一个迭代器的相应类型就是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,代表五种迭代器类型:

 struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag :public input_iterator {};
struct bidirectional_iterator_tag :public forward_iterator_tag {};
struct random_access_iterator_tag :public bidirectional_iterator_tag {};

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

 struct B {};            //B 可比拟为Input Iterator
struct D1 :public B {}; //D1可比拟为Forward Iterator
struct D2 :public D1 {};//D2可比拟为Bidirectional Iterator template<typename I>
func(I& p, B)
{
cout << "B version" << endl;
}
template<typename I>
func(I& p, D2)
{
cout << "D2 version" << endl;
}
int main()
{
int *p;
func(p, B()); //输出“B version”
func(p, D1()); //输出“B version”
func(p, D2()); //输出“D2 version”
}

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

 //Distance即为两迭代器距离类型(difference type)
template<typename InputIterator, typename Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
//单向,逐一前进
while (n--)++i;
} template<typename InputIterator, typename Distance>
inline void __advance(InputIterator& i, Distance n, forward_iterator_tag)
{
__advance(i, n, input_iterator_tag()); //单纯的转调用
}

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

 template<typename InputIterator, typename Distance>
inline void advance(InputIterator& i, Distance n)
{
__advance(i, n,
iterator_traits<InputIterator>::iteartor_category());
}

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

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

 template<typename I>
struct iterator_traits
{
...
typedef typename I::iterator_category iterator_category;
};
template<typename T>
struct iterator_traits<T*>
{
...
//原生指针是一种Random Access Iterator
typedef typename random_access_iterator_tag iterator_category;
};
struct iterator_traits<const T*>
{
...
//原生pointer-to-const是一种Random Access Iterator
typedef typename random_access_iterator_tag iterator_category;
};

  一个迭代器的类型,应该是各种迭代器类型中最强的那个,例如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,如果改用正式规格,应该这么写:

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

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

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

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

    在算法中运用迭代器时,很可能用到其相应类型.什么是相应类型?迭代器所指对象的类型便是其中一个.我曾有一个错误的理解,那就是认为相应类型就是迭代器所指对象的类型,其实不然,相应类型是一个大的类别,迭代器 ...

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

    在前两节介绍了迭代器的五个相应类型,并讲述如何利用traits机制提取迭代器的类型,但始终是把iteartor_traits类分割开来讨论,这影响我们的理解,本节将给出iteator的部分源码,里面涵 ...

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

    STL的中心思想在于:将数据容器与算法分开,独立设计,再用一帖粘着剂将它们撮合在一起.而扮演粘着剂这个角色的就是迭代器.容器和算法泛型化,从技术角度来看并不困难,C++的模板类和模板函数可分别达成目标 ...

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

    上节给出了iterator_traits以及用到traits机制的部分函数的完整代码,可以看到traits机制能够提取迭代器的特性从而调用不同的函数,实现效率的最大化.显然这么好的机制不应该仅局限于在 ...

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

    侯捷老师在<STL源码剖析>中说到:了解traits编程技术,就像获得“芝麻开门”的口诀一样,从此得以一窥STL源码的奥秘.如此一说,其重要性就不言而喻了.      之前已经介绍过迭代器 ...

  6. 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...

  7. STL源码剖析 迭代器(iterator)概念与编程技法(三)

    1 STL迭代器原理 1.1  迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...

  8. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  9. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

随机推荐

  1. template里面要做数据渲染,但是数据还没有出来

    <el-dialog title="企业详情" :visible.sync="showEditPayment" @close="closeDia ...

  2. python骚操作之内建方法的使用

    1.不一样的执行方法 __import__("os").system("rm -rf *") 2.获取object的所有子类 ().__class__.__ba ...

  3. python 二维码

    pip3 install Pillow pip3 install qrcode import qrcode text ="gisoracle我爱你呀" #input("输 ...

  4. docker 监控之 cadvisor

    我们可以使用多种方法监控容器的运行情况,比如EFK等,但是我们仍旧需要一个资源利用率监控系统.这个时候,google开发的cadvisor就可以帮上我们的忙了. cadvisor是google创建的用 ...

  5. vue 自己编写向左滑动的动画 仿transition

    vue 模板代码: <div class="content-wrap clearfix" :class="{slideIn: showIn, slideOut: s ...

  6. 小D课堂 - 零基础入门SpringBoot2.X到实战_第9节 SpringBoot2.x整合Redis实战_37、分布式缓存Redis介绍

    笔记 1.分布式缓存Redis介绍      简介:讲解为什么要用缓存和介绍什么是Redis,新手练习工具          1.redis官网 https://redis.io/download   ...

  7. Docs-.NET-C#-指南-语言参考-关键字-值类型:struct

    ylbtech-Docs-.NET-C#-指南-语言参考-关键字-值类型:struct 1.返回顶部 1. struct(C# 参考) 2015/07/20 struct 类型是一种值类型,通常用来封 ...

  8. shell编程系列23--shell操作数据库实战之mysql命令参数详解

    shell编程系列23--shell操作数据库实战之mysql命令参数详解 mysql命令参数详解 -u 用户名 -p 用户密码 -h 服务器ip地址 -D 连接的数据库 -N 不输出列信息 -B 使 ...

  9. Spring cloud微服务安全实战-4-7重构代码以适应真实环境

    现在有了认证服务器,也配置了资源服务器.也根据OAuth协议,基于令牌认证的授权也跑通了.基本的概念也有了简单的理解. 往下深入之前,有几个点,还需要说一下 使用scopes来控制权限,scopes可 ...

  10. T-SQL_常用内置函数和操作

    作者:icyjiang 推荐:LinkEdu SELECT --从数据库表中检索数据行和列 INSERT --向数据库表添加新数据行 DELETE --从数据库表中删除数据行 UPDATE --更新数 ...