迭代器iterator和traits编程技法
前言
这段时间研读SGI-STL-v2.91源码,并提炼核心代码自己实现一遍,感觉受益颇深。觉得有必要写一些文章记录下学习过程的思考,行文旨在总结,会大量参考侯捷《STL源码剖析》的内容。
迭代器概述
迭代器定义:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而无需暴露该聚合物的内部表述方式。
STL的中心思想在于:将数据容器和算法分开,彼此独立设计,然后再以一贴胶着剂撮合在一起。在STL中,迭代器就扮演着胶着剂的作用。迭代器是一种类似指针的对象,而指针的各种行为中最常见也是最重要的便是解引用和成员访问,因此迭代器最要的编程工作就是对operator*和operator->进行重载。
泛型编程和面向对象编程
认识迭代器之前我觉得有必要了解一下C++中两个非常重要的编程范式,即GP(泛型编程)和OOP(面向对象编程)。
泛型编程:亦称为"静态多态",多种数据类型在同一种算法或者结构上皆可操作,其效率与针对某特定数据类型而设计的算法或结构相同, 具体数据类型在编译期确定,编译器承担更多,代码执行效率高。在STL中利用GP将methods和datas实现了分而治之。
面向对象编程:将methods和datas关联到一起 ,也就是方法和成员变量放到一个类中实现,通过继承的方式,利用虚函数表(virtual)实现运行时类型判定,也叫"动态多态", 由于运行过程中需根据类型去检索虚函数表,因此效率相对较低。
STL全部采用泛型编程,因此,相对来说STL的效率较高且易于维护。
萃取编程技法
在STL算法中需要用到迭代器相应型别,即迭代器所指对象的型别。但C++只支持 sizeof() 并未支持 typeof() 。STL的解决方法之一是利用 function template 的参数推导机制,但 value type 必须用于函数的返回值时 function template 就束手无策。STL利用class类的声明内嵌型别以及针对原生指针的偏特化方式,设计迭代器的萃取机萃取迭代器的特性,解决用于函数返回值的问题,value type 是迭代器的特性之一。下面是STL源码利用class类的声明内嵌型别设计的萃取机,也是萃取机的泛化版本:
//STL萃取器的泛化版本
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;
};
因为原生指针不是class type,无法为它定义内嵌型别。STL利用C++偏特化的方式为泛化设计提供一个特化版本,即将泛化版本中的某些template参数赋予明确的指定。下面是STL源码针对原生指针T*设计的特化版本萃取机,源码还设计了一个const T*原生指针的特化版本,与T*类型这里不再呈现源码:
//针对T*类型的原生指针设计的特化版本
template<class T>
struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_categoty;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
然后STL源码设计了一个获取迭代器 value type 的全局函数,将迭代器作为参数,函数可返回迭代器的型别,具体源代码如下:
template<class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&) {
//根据掺入的Iterator是class type还是T*或const T*原生指针调用泛化或特化的iterator_traits
return static_cast<typename iterator_traits<Iterator>::value_type*>();
}
源码还设计了获取迭代器其他特性 iterator_categoty 和 distance_type 的全局函数,和value_type类似,源码如下:
template<class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
typedef typename iterator_traits<Iterator>::iterator_category category;
return category();
} template<class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&) {
return static_cast<typename iterator_traits<Iterator>::difference_type*>();
}
这种萃取编程技法是STL迭代器实现最重要的方法,萃取机能够萃取各种迭代器的特性。正是这种技法弥补了C++没有typeof()功能的缺陷,让迭代器能够真正脱离于容器,应用于算法中去。
迭代器分类和从属关系
STL源码中定义五个classes代表五种迭代器的型别,定义如下:
//五种迭代器型别
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 {};
这些classes只作为标记用,所以不需要任何成员。这五种迭代器型别的关系如下图所示:
迭代器型别巧用--函数重载
上图能够非常明确看出迭代器的分类和从属关系,设计算法时,如有可能,我们尽量针对上图的某种迭代器提供一个明确定义,并针对更强化的某种迭代器提供另一种定义,保证在不同情况下提供最大效率。在源码中 advance 算法的实现非常能体现这一设计原则。函数入口和针对不同迭代器不同的定义的源码如下:
//advance函数入口
template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
__advance(i, n, iterator_category(i));
} //InputIterator类型定义
template<class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
while (n--) ++i;
} //BidirectionalIterator类型定义
template<class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n,
bidirectional_iterator_tag) {
if (n >= )
while (n--) ++i;
else
while (n++) --i;
} //RandomAccessIterator类型定义
template<class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
random_access_iterator_tag) {
i += n;
}
上面的源码分别对 InputIterator、BidirectionalIterator 和 RandomAccessIterator 三种类型的迭代器设计了不同的 __advance 定义,根据第三参数的不同,使函数形成重载。第三参数只是声明型别并未指定参数名称,因为它纯粹只是用来激活重载机制,函数根本不使用该参数。从源码可以看出 advance 函数根据 iterator_category 获取迭代器型别而重载不同的 __advance 函数,而函数的重载过程是发生在编译期,所以函数的运行效率非常高。
行文至此,我认为已经将STL源码中迭代器和和萃取编程技巧的核心思想总结出来,具体的源码实现可以参考我github的实现:https://github.com/evenleo/sgi-stl-even,关于迭代器的源码在 stl_iterator.h 文件中。
迭代器iterator和traits编程技法的更多相关文章
- STL源码--iterator和traits编程技法
第一部分 iterator学习 STL iterators定义: 提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式. 任何iteartor都应该提供5 ...
- 迭代器概念与traits编程技法
//迭代器是一种smart pointer template<typename T> class ListItem { public: T value() const { return _ ...
- STL源码分析读书笔记--第三章--迭代器(iterator)概念与traits编程技法
1.准备知识 typename用法 用法1:等效于模板编程中的class 用法2:用于显式地告诉编译器接下来的名称是类型名,对于这个区分,下面的参考链接中说得好,如果编译器不知道 T::bar 是类型 ...
- 迭代器(iterator) 与 traits 编程技法
看了候哥的<STL源码剖析>的迭代器那一章,在这里将思路稍微疏理一下 迭代器 迭代器模式的定义:提供一种方法,在不需要暴露某个容器的内部表现形式情况下,使之能依次访问该容器中的各个元素. ...
- STL——迭代器与traits编程技法
一.迭代器 1. 迭代器设计思维——STL关键所在 在<Design Patterns>一书中对iterator模式定义如下:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素 ...
- 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法
大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...
- STL Traits编程技法
traits编程技法大量运用于STL实现中.通过它在一定程度上弥补了C++不是强型别语言的遗憾,增强了C++关于型别认证方面的能力. traits编程技法是利用“内嵌型别”的编程技法和编译器的temp ...
- STL中的Traits编程技法
最近在看读<STL源码剖析>,看到Traits编程技法这节时,不禁感慨STL源码作者的创新能力.那么什么是Traits编程技法呢?且听我娓娓道来: 我们知道容器的许多操作都是通过迭代器展开 ...
- STL源码之traits编程技法
摘要 主要讨论如何获取迭代器相应型别.使用迭代器时,很可能用到其型别,若需要声明某个迭代器所指对象的型别的变量,该如何解决.方法如下: function template的参数推导机制 例如: tem ...
随机推荐
- oracle 分配表权限给用户的写法
grant select on xxxx.xxxxx_TB to sb;grant select on xxxx.xxxxxx_tb to sb;
- 牛客练习赛52 B题【树状数组维护区间和{查询区间和,如果区间元素重复出现则计数一次}】补题ing
[题目] 查询区间和,如果区间元素重复出现则计数一次. 链接:https://ac.nowcoder.com/acm/contest/1084/B [题解] 将询问按r排序,维护每个数最后出现的位置, ...
- java+上传整个文件夹的所有文件
我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 首先我们需要了解的是上传文件三要素: 1.表单提交方式:post (get方式提交有大小 ...
- python一些问题
1.对于字符变量来说不需要深度复制,字符变量是不能改变的 2.文件读取结尾的判断是通过判读 line=self.fd.readline() if not line: //结束了 不用通过判断字符长度. ...
- python利用pybind11调用PCL点云库
2019年7月9日14:31:13 完成了一个简单的小例子,python生成点云数据,利用pybind11传给PCL显示. ubuntu 16.04 + Anaconda3 python3.6 + ...
- ie11 div不显示背景颜色解决方案
我的一个场景就是,一个空的div,但是想加个背景颜色,方案就是在div加个空content,利用before属性加上背景<div class="hilan"></ ...
- 【洛谷2057】 [SHOI2007]善意的投票(最小割)
传送门 洛谷 Solution 比较巧妙啊! 考虑这个只有同意和不统一两种,所以直接令\(s\)表示选,\(t\)表示不选,然后在朋友直接建双向边就好了. 代码实现 #include<bits/ ...
- usage memcache in linux
set和add的区别 set可以重写存在的键值对, 也可以添加新的/ 而add不行, 如果存在已有的键名, 则add不会做更新该键值对, 不做任何事, 就是一次无效操作, 也就是, add可以防止重写 ...
- shell 脚本基础与条件判断
#!shell脚本格式决定专业性 #!/bin/bash #filename:脚本名 #author:作者 #date:时间 #脚本作用 脚本的执行方式 #脚本名为wk.sh 绝对路径 /root/ ...
- LC 873. Length of Longest Fibonacci Subsequence
A sequence X_1, X_2, ..., X_n is fibonacci-like if: n >= 3 X_i + X_{i+1} = X_{i+2} for all i + 2 ...