- iterator模式定义如下:提供一种方法,使之能够依序巡访某个 聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式.
- STL的中心思想在于:将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。容器和算法的泛型化,从技术角度来看并不困难,C ++的class templates和 function templates可分别达成目标。如何设计出两者之间的良好胶着剂,才是大难题。
迭代器 (iterator ) 是一种 smart pointer
- 迭代器是一种行为类似指针的对象,而指针的各种行为中最常见也最重要的便是内容提领〈dereference)和成员访问(member access) , 因此,迭代器最重要的 编程工作就是对 operator* 和 operator-> 进行重载(overloading) 工作。关于 这一点,C++标准程序库有一个auto_ptr可供我们参考任何一本详尽的C++ 语法书籍都应该谈到auto_ptr 这是一个用来包装原生指 针 (native pointer)的对象,声名狼藉的内存漏洞(memory leak)问题可藉此获得解决. auto_ptr用法如下,和原生指针一模一样:
- 函数第一行的意思是,以算式new动态配置一个初值为"jjhou” 的 string 对象,并将所得结果(一个原生指针)作为aUtoj>tr<Strin g > 对象的初值。注意, auto_ptr角括号内放的是“原生指针所指对象”的型别,而不是原生指针的型别.
迭代器相 应 型 别 ( associated types )
- 我们以func ()为对外接口,却把实际操作全部置于func_impl()之中.由于 func_impl ()是一个function template,—旦被调用,编译器会自动进行template参数推导。于是导出型别T,顺利解决了问题。
- 迭代器相应型别(associatedtypes)不 只 是 “迭代器所指对象的型别”〜种而 已。根据经验,最常用的相应型别有五种,然而并非任何情况下任何一种都可利用上述的template参数推导机制来取得.我们需要更全面的解法。
Traits编程技法— STL源代码门钥
- 迭代器所指对象的型别,称为该迭代器的value iy p e .上述的参数型别推导 技巧虽然可用于value ty p e ,却非全面可用:万一 value typ e 必须用于函数的传 回值,就束手无策了,毕竟函数的*"template参数推导机制”推而导之的只是参数,无法推导函数的回返值型别。
- 我们需要其它方法。声明内嵌型别似乎是个好主意,像这样:
- 注 意 ,func ( ) 的回返型别必须加上关键词typename , 因 为 T 是一个 template参数,在它被编译器具现化之前,编译器对T 一无所悉,换句话说,编 译器此时并不知道MyIter<T>: : value_type代表的是一个型别或是一个member function或是一个data m e m b e r . 关 键 词 typename的用意在于告诉编译器这是一个型别,如此才能顺利通过编译。
- 大致的意义是:如 果 class template拥有一个以上的 template参数,我们可以针对其中某个(或数个,但非全部)template参数进行特化工作。换句话说,我们可以在泛化设计中提供一个特化版本(也就是将泛化版本中的某些template参数赋予明确的指定)。
- 多了一层间接性,先前使用一个T,现在使用了两个T
- 但这除了多一层间接性,又带来什么好处呢?好处是traits可以拥有特化版本。现在,我们令 iterator_traites 拥有一个 partial specializations 如下
- 于是,原生指针i n t * 虽然不是一种class type,亦可通过traits取 其 value type。这就解决了先前的问题。
- 但是请注意,针 对 “指向常数对象的指针(pointeEo-snst) ”,下面这个 式子得到什么结果
- iterator_traits<const int*>::value_type
- 获得的是const i n t 而非i n t . 这是我们期望的吗?我们希望利用这种机制来声 明一个暂时变量,使其型别与迭代器的value type相同,而现在,声明一个无法 赋 值 (因 c o n s t 之故)的暂时变量,没什么用!因此,如果迭代器是个pointer-to-const,我们应该设法令其value t y p e 为一个non-const型别。没问题,只要另外设计一个特化版本,就能解决这个问题:
- 现在,不论面对的是迭代器My I t e r , 或是原生指针i n t * 或 const int*,都可以通过traits取出正确的(我们所期望的)value type。
- 特性萃取机”角色,萃取各个迭代器的特性。 这里所谓的迭代器特性,指的是迭代器的相应型别(associated types)。当然,若 要 这 个 “特性萃取机” traits能够有效运作,每一个迭代器必须遵循约定,自行以内嵌型别定义(nested typedef)的方式定义出相应型别(associated types) 。这是一个约定,谁不遵守这个约定,谁就不能兼容于S T L 这个大家庭。
- 根据经验,最常用到的迭代器相应型别有五种:value 1ype, difference iype, pointer, reference, iterator catagoly。如果你希望你所开发的容器能与STL水乳交融,一定要为你的容器的迭代器定义这五种相应型别。 “特性萃取机” traits会很 •实地将原汁原味榨取出来:
3.4.2 迭 代 器 相应型别之二:difference type
- difference type用来表示两个迭代器之间的距离,因此它也可以用来表示一个 容器的最大容量,因为对于连续空间的容器而言,头尾之间的距离就是其最大容量.
- 如果一个泛型算法提供计数功能,例如STL的 count(), 其传回值就必须使用迭代器的 difference type:
- 针对相应型别difference type, traits的如下两个(针对原生指针而写的)特 化版本,以 C++内建的ptrdiff_t (定义于<cstddef>头文件)作为原生指针的 difference type:
- ptrdiff_t是C/C++标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果
3.4.3 迭代器相应型别之三:reference type
- 从 “迭代器所指之物的内容是否允许改变”的角度观之,迭代器分为两种:不 允 许 改 变 “所指对象之内容”者,称为constant iterators,例 如 const int*pic; 允许改变"所指对象之内容”者,称 为 mutable iterators, 例 如 int* pio当我们对 一 个 mutable iterators进行提领操作时,获得的不应该是一个右值(rvalue), 应该是一个左值(lvalue), 因为右值不允许赋值操作(assignm ent), 左值才允许:
- 在 C ++中,函数如果要传回左值,都是以by reference的方式进行,所以当p 是 个 mutable iterators时,如果其value type 是 T,那 么 * p 的型别不应该是T, 应该是T&
- 将此道理扩充,如果p 是 一 个 constant iterators,其 value type 是 t ,那 么 * p 的型别不应该是const T , 而应该是const T&。这里所讨论的* p 的型 别,即所谓的reference type
迭代器相应型别之四:pointer type
- pointers和 references在 C + + 中有非常密切的关联。如 果 “传回一个左值, 令它代表P 所指之物”是可能的,那 么 “传回一个左值,令它代表P 所指之物的地址”也一定可以。也就是说,我们能够传回一个pointer,指向迭代器所指之物。
- item& 便是 Listlier 的 reference type ,而 item * 便是其 pointer type
迭代器 相 应 型 别 之 五 :iterator_category
- 最后一个(第五个)迭代器的相应型别会引发较大规模的写代码工程。在那之前,我必须先讨论迭代器的分类
- 根据移动特性与施行操作,迭代器被分为五类:
- 尽量针对图3・ 2中的某种迭代器提供一个明确定义,并针对更强化的某种迭代器提供另一种定义,这样才能在不同情况下提供最大效率。在研究STL的过程中,每一分每一秒我们都要谨记在心,效率是个重要课题。假设有个算法可接受Forward Iterator,你 以 Random Access Iterator喂给它, 它当然也会接受,因为一个Random Access Iterator必然是一个Forward Iterator(见图3-2) 。但是可用并不代表最佳!
以 advanced。 为例
- 拿 advance () 来 说 (这是许多算法内部常用的一个函数),该函数有两个 参数,迭代器P 和数值n;函数内部将p 累进n 次 (前进n 距离)。下面有三份定义,一份针对 Input Iterator, 一份针对 Bidirectional Iterator,另一份针对 Random Access Iteratoro倒是没有针对Foiv/ardlterator而设计的版本,因为那和 针 对 Inputiterator而设计的版本完全~致。
- 设计考虑如下:如 果 traits有能力萃取出迭代器的种类,我们便可利用这个 “迭代器类型”相应型别作为advanced 0 的第三参数。这个相应型别一定必须是一个class type,不能只是数值号码类的东西,因为编译器需仰赖它(一个型别)来进行重载决议(overloaded resolution) o 下面定义五个classes,代表五种迭代器类型:
- 这 些 classes只作为标记用,所以不需要任何成员。至于为什么运用继承机制, 稍后再解释。现在重新设计— advance。 (由于只在内部使用,所以函数名称加 上特定的前导符),并加上第三参数,使它们形成重载:
- 注意上述语法,每个— advanced 的最后一个参数都只声明型别,并未指定 参数名称,因为它纯粹只是用来激活重载机制,函数之中根本不使用该参数。如果硬要加上参数名称也可以,画蛇添足罢了。
- 行进至此,还需要一个对外开放的上层控制接口,调用上述各个重载的_advance()。这一上层接口只需两个参数,当它准备将工作转给上述的 _advance ()时,才自行加上第三参数:迭代器类型。因此,这个上层函数必须有 能力从它所获得的迭代器中推导出其类型—— 这份工作自然是交给traits机制:
- 任何一个迭代器,其类型永远应该落在“该迭代器所隶属之各种类型中,最强化的那个”。例如,int* 既是 Random Access Iterator,又是 Bidirectional Iterator, 同时也是Forward Iterator,而且也是Input Iterator,那么,其类型应该归属为 random_access_iterator_tag
- 按 说 advanced()既然可以接受各种类型的迭代器,就不应将其型别参数命 名为Inputiterator。这其实是STL算法的一个命名规则:以算法所能接受之最 低阶迭代器类型,来为其迭代器型别参数命名。
- 消 除 "单纯传递调用的函数
- 以 class来定义迭代器的各种分类标签,不仅可以促成重载机制的成功运作 (使编译器得以正确执行重载决议,overloaded resolution), 另一个好处是,通过继承,我们 可 以 不 必 再 写 “单纯只做传递调用”的函数(例如前述的 advance() Forwarditerator版 ) 。为什么能够如此?考虑下面这个小例子,从其输出结果可以 看出端倪:
以 d is ta n c e ()为 例
- 关 于 “迭代器类型标签”的应用,以下再举一例。distance ()也是常用的一个迭代器操作函数,用来计算两个迭代器之间的距离。针对不同的迭代器类型,它可以有不同的计算方式,带来不同的效率。整个设计模式和前述的advance ()如出一辙:
- distance使用 category动态适配 InputIterator和randomAccessiterator,分别调用与之匹配的__distance函数,但是这个 distance使用的时候 <> 需要指定最小的迭代器类型,来为迭代器进行命名
- 注意,distanced可接受任何类型的迭代器;其 template型别参数之所以命 名 为 Inputiterator,是为了遵循STL算法的命名规则:以算法所能接受之最初 级类型来为其迭代器型别参数命名.此外也请注意,由于迭代器类型之间存在着继承关系, “传递调用(forwarding) ”的行为模式因此自然存在一 一点我已在 前一节讨论过。换句话说,当客端调用distanced并使用Output Iterators或Forward Iterators 或 BidirectionaI Iterators 时,统统都会传递调用 Input Iterator 版
的那个_ distance ( ) 函数。
std::iterator 的保证
- 了符合规范,任何迭代器都应该提供五个内嵌相应型别,以利于traits萃取,否则便是自别于整个STL架构,可能无法与其它STL组件顺利搭配。然而写代码难免挂一漏万,谁也不能保证不会有粗心大意的时候。如果能够将事情简化,就好多了。STL提供了一个iterators class如下,如果每个新设计的迭代器都 继承自它,就可保证符合STL所需之规范:
- 设计适当的相应型别(associated types) , 是迭代器的责任。设计适当的迭代 器,则是容器的责任.唯容器本身,才知道该设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为(前进、后退、取值、取用成员…) 。至于算法,完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就行。
ite ra to r源代码完整重列
- SGI STL<stl_iterator.h>头文件内与本章相关的程序代码。该头文件还有其它内容,是关于 iostream iterators, inserter iterators 以及 reverse iterators 的实现,将于第8 章讨论。
我并不是很懂为啥要再包装一层
SGI STL 的 私 房 菜 :__type_traits
- traits编程技法很棒,适度弥补了 C++语言本身的不足。STL只对迭代器加 以规范,制定出itera to r_ tra its这样的东西。SG I把这种技法进一步扩大到迭 代器以外的世界,于是有了所谓的_ type_tra-itso 双底线前缀词意指这是SGI STL内部所用的东西,不在STL标准规范之内
- iterator_ traits负责萃取迭代器的特性,—type_ traits则负责萃取型别(type)的特性
- 此处我们所关注的型别特性是指:这个型别是否具备non-trivial defaltctor ? 是否具备 non-trivial copy ctor ? 是否具备 non-trivial assignment operator?是否具备non-trivial dtor?如果答案是否定的,我们在对这个型别进行构造、析构、拷贝、赋值等操作时,就可以采用最有效率的措施(例如根本不调用身居高位,不谋实事的那些constructor, destructor), 而采用内存直接处理操作如 malloc. memcpy等等,获得最高效率。这对于大规模而操作频繁的容器, 有着显著的效率提升4。
- 我们希望上述式子响应我们“真”或 “假”(以便我们决定采取什么策略),但其结果不应该只是个bool值,应该是个有着真/假性质的“对象”,因为我们 希望利用其响应结果来进行参数推导,而编译器只有面对Class object形式的参数, 才会做参数推导。为此,上述式子应该传回这样的东西:
- 为了达成上述五个式子,— type_traits内必须定义一些typedefs,其值不是 _ true_type 就是 _ false_type下面是 SGI 的做法:
POD
例子