SGL STL源码剖析——迭代器

2023-10-26

迭代器

在我们使用STL容器的时候,迭代器是非常常见的,STL将容器和算法分开,彼此独立,然后通过迭代器相互关联。迭代器不是脱离容器存在的,每一种STL容器都提供专属迭代器,比如我们经常使用的find算法,

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
    vector<int> array = {1, 2, 3, 4, 5, 6};
    vector<int>::iterator iter = find(array.begin(), array.end(), 3);
    if(iter != array.end())
    {
        cout << "success"<<endl;
    }
    return 0;
}

算法要使用迭代器,而迭代器依附于容器,换句话说,迭代器要通过容器来定义。原书中有讨论将迭代器独立的实现,但是这种实现会暴露容器的细节,并不合适。这里我们不多看,直接看源码中迭代器的实现方式。

迭代器的型别

如前面所述,迭代器依赖于容器,在算法中可能要获取迭代器的型别才能实现细节。但是C++并不支持返回类型,比如sizeof可以返回类型的大小,typeid也只是获得型别名称,但是这个名称不能直接拿来做变量声明。所以我们第一个要弄懂的就是如果进行类型推断。
做法其实很简单,利用模板的参数推导机制。
我们在写函数模板的时候是不知道类型的,比如,

template<class I, class T>
void func_detail(I iter, T t)
{
    T tmp;
}
template<class I>
void func(I iter)
{
    func_detail(iter, *iter);
}
int main()
{
    int i;
    func(&i);
}

这里的模板函数func,使用了模板参数I iter。注意这里我们只知道迭代器的类型是I,但是我们并不知道迭代器所指对象的型别,所以我们再封装一个func_detail,把T t作为模板参数,然后传入解引用的迭代器,这样编译器就会推断出类型T。

Traits的作用

前面说了利用模板参数进行类型推导,我们推断的是迭代器所指对象的类型,称为value type。如果说我们要用value type作为函数返回值,那么前面的做法就不行了。这时候我们要用到内嵌类型,这在所有的容器中都用定义,

template<class T>
struct MyIter
{
    typedef T value_type; //用这个内嵌类型把T的类型暴露出去
    T* ptr;
    MyIter(T* p = 0):ptr(p){}
    T& operator* () const{return *ptr;}
};
template<class I>
typename I::value_type func(I iter)
{
    return *iter;
}
int main()
{
    MyIter<int> iter(new int (8));
    cout << func(iter) <<endl;
}

可以看到,func这个函数要返回迭代器所指对象的类型,所以我们用了一个内嵌类型,因为T是一个模板参数,所以必须要用typename告诉编译器这是一个类型,否则编译不过。
上面这个中做法有什么缺陷呢,如上像上面的那个例子一样,要传入&i作为迭代器参数,那就不行了,&i是int型指针,是一个原生指针,无法为它定义内嵌型别。为此我们需要针对原生指针这个特殊情况作特化处理,这就是template partial specialization
所谓偏特化就是针对部分模板参数进行特化,

template<typename T>
class C{ ... }
// 针对模板参数的特化版本T*
template<typename T>
class C<T*>{ ... }

这样就相当于我们定义了两个模板,如果实例化传入的是原生指针,那就走到下面这个定义,这就是特化。
那这样有什么用呢,其实是为了引出下面这个封装的traits,

template<class I>
struct iterator_traits
{
    typedef typename I::value_type value_type;
};

这个模板,如果I是一个class type,有自己的value_type,traits就会萃取这个value_type,如果没有咋办呢,那就再定义一个特化版本的模板,

template<class T>
struct iterator_traits<T*>
{
    typedef T value_type;
};

这个特化的版本接受原生指针,比如int *,那它的value_type就是int,这样的话traits也能萃取出value_type。
这样的话,前面那个func就可以写成,

template<class I>
typename iterator_traits<I>::value_type func(I iter)
{
    return *iter;
}

这时候我们还漏了一种情况,就是const指针,即指向常数熟对象的指针,由于要作为返回值类型,那肯定不能萃取出一个const的value_type,所以要再设计一个特化版本,

template<class T>
struct iterator_traits<const T*>
{
    typedef T value_type;
};

好了,到这里我们就完全明白了iterator_traits这个类型萃取的实现原理了,每一个STL容器都会定义内嵌类型以便traits能正确运作。与迭代器相关的类型不止有所指向对象类型,一共有五种:value_type, difference_type, pointer, reference, iterator catagoly,我们可以先看下vector这个容器的定义,

template <class T, class Alloc = alloc>
class vector
{
public:
	typedef T value_type;
	typedef value_type* pointer;
	typedef value_type* iterator;
	typedef value_type& reference;
	typedef size_t& size_type;
	typedef ptrdiff_t difference_type;

所以,traints要把这5中特性都提取出来,

template<class I>
struct iterator_traits
{
	typedef typename I::iterator_category 	iterator_category;
    typedef typename I::value_type 			value_type;
    typedef typename I::difference_type 	difference_type;
    typedef typename I::pointer				pointer;
    typedef typename I::reference			reference;
};

如前面所述,针对原生指针和const指针,必须要有实现特化版本,

template<class T>
struct iterator_traits<T*>
{
	typedef random_access_iterator_tag 		iterator_category;
    typedef T			value_type;
    typedef ptrdiff_t 	difference_type;
    typedef T*			pointer;
    typedef T&			reference;
};

迭代器相应的五种型别

value_type
value_type就是迭代器所指对象的类型,前面已经说过,每个STL容器都定义了自己的内嵌类型。

difference_type
difference_type表示两个迭代器之间的距离,比如count算法使用的返回值类型就是difference_type,

template<class I, class T>
typename iterator_traits<I>::difference_type 
count(I first, I last, const T& value)
{
	typename iterator_traits<I>::difference_type n = 0;
	for( ; first != last; ++first)
		if(*first == value)
			++n;
	return n;
}

difference_type的特化版本直接使用ptrdiff_t实现。
reference_type
reference_type就是引用类型

point_type
指针类型

iterator_category
这个类型比较复杂点儿,先来看一下迭代器的分类,
Input Iterator: 只读,迭代器所指的对象不允许外界改变。
Output Iterator: 只写
Forward Iterator: 允许写入型算法在此种迭代器所形成的区间上进行读写操作
Bidirectional Iterator: 可双向移动
Random Access Iterator: 前面几种迭代器只支持++和–的操作,这种则支持算法运算如p + n
上面这五种迭代器除了前面两个并列,剩下的依次从属。
在这里插入图片描述
我们举一个例子,看看Random Access Iterato究竟有什么用,

//接受Input Iterator
template<class InputIterator, class Distance>
void advance_II(InputIterator& i, Distance n)
{
    while(n--) ++i;
};

template<class BidirectionalIterator, class Distance>
void advance_BI(BidirectionalIterator& i, Distance n)
{
    if(n >= 0)
        while (n--) ++i;
    else 
        while (n++) --i;
};

template<class RandomAccessIterator, class Distance>
void advance_RAI(RandomAccessIterator& i, Distance n)
{
    i += n;
};

//在运行时决定调用哪个版本
template<class InputIterator, class Distance>
void advance(InputIterator& i, Distance n)
{
    if(is_random_access_iterator(i))
        advance_RAI(i, n);
    else if(is_bidirectional_iterator(i))
        advance_BI(i, n);
    else
        advance_II(i, n);
}

为了应对多种迭代器类型,我们需要实现多个函数,并且封装一个统一的转调用接口。那么能不能通过重载实现呢,当然不行,因为上面的多个版本都是两个模板参数,模板参数本身都是表示未定的类型,那当然不能重载了。为此,我们需要再追加一个参数。
自然的,我们会想到利用前面说的traits萃取出迭代器的种类。
先定义处5中迭代器类型,

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 {};

继承关系和前面所说的5中迭代器从属关系完全一致。
然后我们先实现多个迭代器类型的内部转调用版本,

template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
  while (__n--) ++__i;
}
template <class _ForwardIterator, class _Distance>
inline void __advance(_ForwardIterator& __i, _Distance __n, 
_forwardIterator_iterator_tag) {
//转调用
  __advance(__i, __n, input_iterator_tag());
}
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n, 
                      bidirectional_iterator_tag) {
  __STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator);
  if (__n >= 0)
    while (__n--) ++__i;
  else
    while (__n++) --__i;
}
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n, 
                      random_access_iterator_tag) {
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __i += __n;
}

注意上面每个_advance()的最后一个参数都只是声明类型,不需要参数,因为只是用来重载,函数中并不需要该参数。
然后满在封装一个对外的接口,

template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
  __STL_REQUIRES(_InputIterator, _InputIterator);
  __advance(__i, __n, iterator_category(__i));
}

到这里我们基本上就明白了iterator_category是要干什么,没错,就是根据我们传入的模板参数_InputIterator& __i 返回一个对应的迭代器类型,

template <class _Tp, class _Distance> 
inline input_iterator_tag 
iterator_category(const input_iterator<_Tp, _Distance>&)
  { return input_iterator_tag(); }

inline output_iterator_tag iterator_category(const output_iterator&)
  { return output_iterator_tag(); }

template <class _Tp, class _Distance> 
inline forward_iterator_tag
iterator_category(const forward_iterator<_Tp, _Distance>&)
  { return forward_iterator_tag(); }

template <class _Tp, class _Distance> 
inline bidirectional_iterator_tag
iterator_category(const bidirectional_iterator<_Tp, _Distance>&)
  { return bidirectional_iterator_tag(); }

template <class _Tp, class _Distance> 
inline random_access_iterator_tag
iterator_category(const random_access_iterator<_Tp, _Distance>&)
  { return random_access_iterator_tag(); }

template <class _Tp>
inline random_access_iterator_tag iterator_category(const _Tp*)
  { return random_access_iterator_tag(); }

iterator_category进行了重载,根据我们传入的_InputIterator& __i的不同,返回不同的_tag对象。等一下,这和书上说的不一样啊,这里好像不需要萃取了,直接根据重载函数就能返回对应对象了。说实话,这里我没看懂。看代码的话,还有一个实现,

template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
iterator_category(const _Iter& __i) { return __iterator_category(__i); }

template <class _Iter>
// 这里其实也是用了模板参数类型推断的方式
inline typename iterator_traits<_Iter>::iterator_category
__iterator_category(const _Iter&)
{
  typedef typename iterator_traits<_Iter>::iterator_category _Category;
  return _Category();
}

貌似这个才和树上说的对应,这里根据传入的迭代器类型去实例化traits,这就是iterator_category的作用。这要容器中定义了iterator_category,比如把random_access_iterator_tag typedef为iterator_category,那么上面typedef typename iterator_traits<_Iter>::iterator_category _Category其实就相当于拿到了类名random_access_iterator_tag ,然后_Category()构造就返回了这个类型的对象。

template<class I>
struct iterator_traits
{
	typedef typename I::iterator_category 	iterator_category;
    typedef typename I::value_type 			value_type;
    typedef typename I::difference_type 	difference_type;
    typedef typename I::pointer				pointer;
    typedef typename I::reference			reference;
};

另外注意命名,STL算法规则:以算法所能接受的最低阶迭代器类型,来为其迭代器型别参数命名。
利用迭代器类型的继承关系,也可以消除转调用。如果一个迭代器符合多个类型,那就以最强类型作为归属,也就是子类型。
综上,我们知道了对于迭代器来说,它要解决的问题就是设计推导相应的型别,而迭代器本身是由容器去定义的。
源码部分,我们把这几个萃取放一起来看

// 迭代器的类别
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
__iterator_category(const _Iter&)
{
  typedef typename iterator_traits<_Iter>::iterator_category _Category;
  return _Category();
}
//迭代器的difference_type
template <class _Iter>
inline typename iterator_traits<_Iter>::difference_type*
__distance_type(const _Iter&)
{
  return static_cast<typename iterator_traits<_Iter>::difference_type*>(0);
}
// 决定某个迭代器的value_type
template <class _Iter>
inline typename iterator_traits<_Iter>::value_type*
__value_type(const _Iter&)
{
  return static_cast<typename iterator_traits<_Iter>::value_type*>(0);
}

__type_traits

前面我们学习了iterator_traits这样的traits编程技巧,SGI中额外拓展了__type_traits,这在上一节的空间配置器中其实已经用到过,接下来我们详细来看。iterator_traits是负责萃取迭代器的特性,而__type_traits则是负责萃取型别的特性。比如空间配置器在destroy中使用的型别是否具有non-trivial 的析构函数,其他类似的还有默认构造函数、复制构造函数、拷贝运算符。为什么要这么区分呢,主要是为了效率问题,对于不具备的类型,我们可以通过memcpy这些内存操作提高效率。其实很好理解,比如如果是类中进行了new或者malloc的操作,那自然不能简单拷贝或者析构了。
这个实现其实非常暴力,
首先定义了两个type,一个true和一个false,和前面的iterator_traits一样,我们要用类型做参数推导,所以不能简单的使用bool值

struct __true_type {};
struct __false_type {};

然后是__type_traits的定义,

template <class _Tp>
struct __type_traits { 
   typedef __true_type     this_dummy_member_must_be_first;
   
   typedef __false_type    has_trivial_default_constructor;
   typedef __false_type    has_trivial_copy_constructor;
   typedef __false_type    has_trivial_assignment_operator;
   typedef __false_type    has_trivial_destructor;
   typedef __false_type    is_POD_type;
};

然后针对特化版本,定义值,比如bool类型

__STL_TEMPLATE_NULL struct __type_traits<bool> {
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

举个例子,uninitialized_fill_n()这个函数.

inline void uninitialized_fill(_ForwardIter __first, _ForwardIter __last,  const _Tp& __x)                                                          
{
	//先利用__VALUE_TYPE萃取出迭代器所指对象类型
	__uninitialized_fill(__first, __last, __x, __VALUE_TYPE(__first));
}
template <class _ForwardIter, class _Tp, class _Tp1>
inline void __uninitialized_fill(_ForwardIter __first, _ForwardIter __last, const _Tp& __x, _Tp1*)                                
{
	//利用模板参数推断得到类型_Tp1,然后实例化__type_traits得到_Is_POD
	typedef typename __type_traits<_Tp1>::is_POD_type _Is_POD;
	__uninitialized_fill_aux(__first, __last, __x, _Is_POD());               
}
//根据_Is_POD()走到不同的分支
template <class _ForwardIter, class _Size, class _Tp>
inline _ForwardIter
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n, const _Tp& __x, __true_type)                         
{
	return fill_n(__first, __n, __x);
}
template <class _ForwardIter, class _Size, class _Tp>
_ForwardIter
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n, const _Tp& __x, __false_type)                           
{
	_ForwardIter __cur = __first;
	__STL_TRY {
		for ( ; __n > 0; --__n, ++__cur)
		_Construct(&*__cur, __x);
		return __cur;
	}
	__STL_UNWIND(_Destroy(__first, __cur));
}

好了,迭代器的学习就到这里了。回顾一下,主要是模板参数类型的推断、traits编程萃取的实现方式特别是iterator_category的定义,最后是__type_traits。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SGL STL源码剖析——迭代器 的相关文章

随机推荐

  • Linux驱动_spi驱动(ICM20608)

    参考 Linux SPI 驱动分析 1 结构框架 StephenZhou CSDN博客 linux spi驱动 Linux SPI 驱动分析 2 框架层源码分析 StephenZhou CSDN博客 spi message init SPI
  • sql server 加密_列级SQL Server加密

    列加密 创建一个新的数据库并创建CustomerInfo表 CREATE DATABASE CustomerData Go USE CustomerData GO CREATE TABLE CustomerData dbo Customer
  • 机器学习/深度学习--手写数字识别(MNIST数据集)

    import torch 导入torchvision的transform模块 用来处理数据集 from torchvision import transforms from torchvision import datasets from
  • WebRTC音视频通话-RTC直播本地视频及相册视频文件

    WebRTC音视频通话 RTC直播本地视频及相册视频文件 WebRTC音视频通话 RTC直播本地视频文件效果图如下 WebRTC音视频通话 RTC直播本地视频文件时候 用到了AVPlayer CADisplayLink 一 通过AVPlay
  • SpringBoot项目部署到阿里云服务器

    一 阿里云 01 开放端口 02 安装jdk 將jar包放到这个目录下 解压缩 并删除安装包 tar zxvf jdk 8u191 linux x64 tar gz rm f jdk 8u191 linux x64 tar gz 设置系统环
  • shell中 >&2含义

    echo this is a test gt 2 gt 2 也就是把结果输出到和标准错误一样 之前如果有定义标准错误重定向到某file文件 那么标准输出也重定向到这个file文件 其中 的意思 可以看成是 The same as 与 一样
  • webpack入门

    webpack入门 webpack简介 模块打包器 项目构建工具 自动化构建工具 将多种类型资源之间的依赖关系构建成统一的静态资源 打包上线部署 js css等 但不包括html 因为它认为html不算模块 四个核心概念 入口entry 输
  • springboot整合shiro实现认证授权源码

    shiro admin 介绍 springboot整合shiro实现前后端分离架构 swagger文档协调前端开发 源码地址 https gitee com liujinxin ark shiro admin 软件架构 架构说明 sprin
  • 【深度学习】 Python 和 NumPy 系列教程(一):Python基本数据类型:1、数字(整数、浮点数)及相关运算;2、布尔值

    目录 一 前言 二 实验环境 三 Python基本数据类型 1 数字 a 整数 int b 浮点数 float c 运算 运算符 增强操作符 代码整合 d 运算中的类型转换 e 运算函数abs max min int float 2 布尔值
  • 如何判断对象中是否存在某个键名

    之前遇到过很多这样的问题 如何去判断对象中是否存在某个键 从而对其进行下一步的操作 下面就就给大家介绍一种我目前了解的一种方法 首先你新建了一个新的对象 var obj 顺便复习一下上次讲的forEach循环 function get so
  • TCP 三次握手和四次挥手的面试题

    重新整理了一版 TCP 三次握手和四次挥手的面试题 2023最新版 任 TCP 虐我千百遍 我仍待 TCP 如初恋 巨巨巨巨长的提纲 发车 发车 img TCP 基本认识 TCP 头格式有哪些 我们先来看看 TCP 头的格式 标注颜色的表示
  • python 编码 —— codecs 库

    1 对文件读写 import codecs fout codecs open test html w encoding UTF 8 fout write fout write fout close 很自然地可将其改造为 with 结构 wi
  • 淘宝TDDL数据库分库分表

    淘宝TDDL数据库分库分表 2014 06 04 23 18 3334人阅读 评论 0 收藏 举报 分类 数据库 1 分库分表 而且分库规则非常灵活 2 主键生成策略 目前TDDL提供的id生成主要还是依托数据库来进行的 oracle可以直
  • 八大排序算法-归并排序

    归并排序的定义 是将两个 或两个以上 有序表合并成一个新的有序表 即把待排序序列分为若干个子序列 每个子序列是有序的 然后再把有序子序列合并为整体有序序列 归并排序的基本思想 设r i n 由两个有序子表r i m 和r m 1 n 组成
  • ref绑定到不同元素获取到不同对象

    ref如果绑定在组件中 那么通过this ref refname获取到的是一个组建对象 ref如果绑定在普通的元素中 那么通过this ref refname获取到的是一个元素对象
  • 云呐

    科技大数据时代 企业的信息化规划刻不容缓 固定资产管理系统做为一款企业资产方案系统 可完成对企业资产的系统化管理 充分发挥资产更高的实用价值 固定资产管理系统可将企业內部全部资产融合在一起 根据对固定资产的增加 改动 退出 迁移 删除 使用
  • 2016年4月28日(6985小时时),第一次签合同,里程碑

    这周四 我觉得是个历史性的事件 是个里程碑 说明 锲而不舍 金石可镂 虽然不多 2万
  • win11/ win10 C盘扩容教程

    win11 win10 C 盘扩容教程 1 写在前面 10月5号微软官方正式发布了win11操作系统 作为一名科技星人 我也是第一时间升级体验了一番 如何升级win11我就不多说了 晚上一搜教程非常的多 这里推荐使用win11升级助手升级
  • 合宙ESP32系列

    目录 源文档见 ESP32系列编译文档 LuatOS 文档 本地编译详细步骤 准备环境 准备项目 获取源码 编译前的最后准备 编译 LuatOS SoC通用固件格式soc介绍 定制固件里的库 PS luat conf bsp h问题汇总 源
  • SGL STL源码剖析——迭代器

    SGL STL源码剖析 迭代器 迭代器 迭代器的型别 Traits的作用 迭代器相应的五种型别 type traits 迭代器 在我们使用STL容器的时候 迭代器是非常常见的 STL将容器和算法分开 彼此独立 然后通过迭代器相互关联 迭代器