前言

这段时间研读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类的声明内嵌型别设计的萃取机,也是萃取机的泛化版本:

  1. //STL萃取器的泛化版本
  2. template<class Iterator>
  3. struct iterator_traits {
  4. typedef typename Iterator::iterator_category iterator_category;
  5. typedef typename Iterator::value_type value_type;
  6. typedef typename Iterator::difference_type difference_type;
  7. typedef typename Iterator::pointer pointer;
  8. typedef typename Iterator::reference reference;
  9. };

因为原生指针不是class type,无法为它定义内嵌型别。STL利用C++偏特化的方式为泛化设计提供一个特化版本,即将泛化版本中的某些template参数赋予明确的指定。下面是STL源码针对原生指针T*设计的特化版本萃取机,源码还设计了一个const T*原生指针的特化版本,与T*类型这里不再呈现源码:

  1. //针对T*类型的原生指针设计的特化版本
  2. template<class T>
  3. struct iterator_traits<T*> {
  4. typedef random_access_iterator_tag iterator_categoty;
  5. typedef T value_type;
  6. typedef ptrdiff_t difference_type;
  7. typedef T* pointer;
  8. typedef T& reference;
  9. };

然后STL源码设计了一个获取迭代器 value type 的全局函数,将迭代器作为参数,函数可返回迭代器的型别,具体源代码如下:

  1. template<class Iterator>
  2. inline typename iterator_traits<Iterator>::value_type*
  3. value_type(const Iterator&) {
  4. //根据掺入的Iterator是class type还是T*或const T*原生指针调用泛化或特化的iterator_traits
  5. return static_cast<typename iterator_traits<Iterator>::value_type*>();
  6. }

源码还设计了获取迭代器其他特性 iterator_categoty 和 distance_type 的全局函数,和value_type类似,源码如下:

  1. template<class Iterator>
  2. inline typename iterator_traits<Iterator>::iterator_category
  3. iterator_category(const Iterator&) {
  4. typedef typename iterator_traits<Iterator>::iterator_category category;
  5. return category();
  6. }
  7.  
  8. template<class Iterator>
  9. inline typename iterator_traits<Iterator>::difference_type*
  10. distance_type(const Iterator&) {
  11. return static_cast<typename iterator_traits<Iterator>::difference_type*>();
  12. }

这种萃取编程技法是STL迭代器实现最重要的方法,萃取机能够萃取各种迭代器的特性。正是这种技法弥补了C++没有typeof()功能的缺陷,让迭代器能够真正脱离于容器,应用于算法中去。

迭代器分类和从属关系

STL源码中定义五个classes代表五种迭代器的型别,定义如下:

  1. //五种迭代器型别
  2. struct input_iterator_tag {};
  3. struct output_iterator_tag {};
  4. struct forward_iterator_tag : public input_iterator_tag {};
  5. struct bidirectional_iterator_tag : public forward_iterator_tag {};
  6. struct random_access_iterator_tag : public bidirectional_iterator_tag {};

这些classes只作为标记用,所以不需要任何成员。这五种迭代器型别的关系如下图所示:

迭代器型别巧用--函数重载

上图能够非常明确看出迭代器的分类和从属关系,设计算法时,如有可能,我们尽量针对上图的某种迭代器提供一个明确定义,并针对更强化的某种迭代器提供另一种定义,保证在不同情况下提供最大效率。在源码中 advance 算法的实现非常能体现这一设计原则。函数入口和针对不同迭代器不同的定义的源码如下:

  1. //advance函数入口
  2. template<class InputIterator, class Distance>
  3. inline void advance(InputIterator& i, Distance n) {
  4. __advance(i, n, iterator_category(i));
  5. }
  6.  
  7. //InputIterator类型定义
  8. template<class InputIterator, class Distance>
  9. inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
  10. while (n--) ++i;
  11. }
  12.  
  13. //BidirectionalIterator类型定义
  14. template<class BidirectionalIterator, class Distance>
  15. inline void __advance(BidirectionalIterator& i, Distance n,
  16. bidirectional_iterator_tag) {
  17. if (n >= )
  18. while (n--) ++i;
  19. else
  20. while (n++) --i;
  21. }
  22.  
  23. //RandomAccessIterator类型定义
  24. template<class RandomAccessIterator, class Distance>
  25. inline void __advance(RandomAccessIterator& i, Distance n,
  26. random_access_iterator_tag) {
  27. i += n;
  28. } 

上面的源码分别对 InputIterator、BidirectionalIterator 和 RandomAccessIterator 三种类型的迭代器设计了不同的 __advance 定义,根据第三参数的不同,使函数形成重载。第三参数只是声明型别并未指定参数名称,因为它纯粹只是用来激活重载机制,函数根本不使用该参数。从源码可以看出 advance 函数根据 iterator_category 获取迭代器型别而重载不同的 __advance 函数,而函数的重载过程是发生在编译期,所以函数的运行效率非常高。

行文至此,我认为已经将STL源码中迭代器和和萃取编程技巧的核心思想总结出来,具体的源码实现可以参考我github的实现:https://github.com/evenleo/sgi-stl-even,关于迭代器的源码在 stl_iterator.h 文件中。

迭代器iterator和traits编程技法的更多相关文章

  1. STL源码--iterator和traits编程技法

    第一部分 iterator学习 STL iterators定义: 提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式. 任何iteartor都应该提供5 ...

  2. 迭代器概念与traits编程技法

    //迭代器是一种smart pointer template<typename T> class ListItem { public: T value() const { return _ ...

  3. STL源码分析读书笔记--第三章--迭代器(iterator)概念与traits编程技法

    1.准备知识 typename用法 用法1:等效于模板编程中的class 用法2:用于显式地告诉编译器接下来的名称是类型名,对于这个区分,下面的参考链接中说得好,如果编译器不知道 T::bar 是类型 ...

  4. 迭代器(iterator) 与 traits 编程技法

    看了候哥的<STL源码剖析>的迭代器那一章,在这里将思路稍微疏理一下 迭代器 迭代器模式的定义:提供一种方法,在不需要暴露某个容器的内部表现形式情况下,使之能依次访问该容器中的各个元素. ...

  5. STL——迭代器与traits编程技法

    一.迭代器 1. 迭代器设计思维——STL关键所在 在<Design Patterns>一书中对iterator模式定义如下:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素 ...

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

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

  7. STL Traits编程技法

    traits编程技法大量运用于STL实现中.通过它在一定程度上弥补了C++不是强型别语言的遗憾,增强了C++关于型别认证方面的能力. traits编程技法是利用“内嵌型别”的编程技法和编译器的temp ...

  8. STL中的Traits编程技法

    最近在看读<STL源码剖析>,看到Traits编程技法这节时,不禁感慨STL源码作者的创新能力.那么什么是Traits编程技法呢?且听我娓娓道来: 我们知道容器的许多操作都是通过迭代器展开 ...

  9. STL源码之traits编程技法

    摘要 主要讨论如何获取迭代器相应型别.使用迭代器时,很可能用到其型别,若需要声明某个迭代器所指对象的型别的变量,该如何解决.方法如下: function template的参数推导机制 例如: tem ...

随机推荐

  1. pypdf2:下载Americanlife网页生成pdf合并pdf并添加书签

    初步熟悉 安装 pip install pypdf2 合并并添加书签 #!/usr/bin/env python3.5 # -*- coding: utf-8 -*- # @Time : 2019/1 ...

  2. css(name|pro|[,val|fn])

    css(name|pro|[,val|fn]) 概述 访问匹配元素的样式属性.大理石平台支架 jQuery 1.8中,当你使用CSS属性在css()或animate()中,我们将根据浏览器自动加上前缀 ...

  3. learning armbian steps(8) ----- armbian 源码分析(三)

    在lib/main.sh当中 ) == main.sh ]]; then echo "Please use compile.sh to start the build process&quo ...

  4. Cogs 1688. [ZJOI2008]树的统计Count(树链剖分+线段树||LCT)

    [ZJOI2008]树的统计Count ★★★ 输入文件:bzoj_1036.in 输出文件:bzoj_1036.out 简单对比 时间限制:5 s 内存限制:162 MB [题目描述] 一棵树上有n ...

  5. C# 父类代码动态转换子类

    百度上搜索C# 如何父类运行时转换成子类,没有得到相应答案,突然想起C# 有dynamic类型试试看结果成功了... 以后编写代码类似这样的代码 就可以删减掉了 if (en.type == EMap ...

  6. OpenGL 开发环境配置:Visual Studio 2017 + GLFW + GLEW

    Step1:Visual Studio 2017 Why 开发环境,后面编译GLFW 和 GLEW也要用 How 这里使用的是Visual Studio 2017的 Community 版本,直接官网 ...

  7. JVM----Class类文件结构

    JVM平台无关性 Java具有平台无关性,也就是任何操作系统都能运行Java代码.之所以能实现这一点,是因为Java运行在虚拟机之上,不同的操作系统都拥有各自的Java虚拟机,因此Java能实现“一次 ...

  8. qt 之http学习

    在Qt网络编程中,需要用到协议,即HTTP.它是超文本传输协议,它是一种文件传输协议. 新建工程名为“http”,然后选中QtNetwork模块,最后Base class选择QWidget.注意:如果 ...

  9. Android ROM适配

    Android是开源的,不同的手机厂商都有自己定制的系统,所以这就给开发者带来了ROM适配难题.在一些群里面经常看到有人因为手机适配问题,说这个手机坑,那个手机坑,其实那是没有对ROM定制系统的一些变 ...

  10. leetcode84 柱状图

    O(n^2) time 应用heights[r]<=heights[r+1]剪枝: class Solution { public: int largestRectangleArea(vecto ...