C++迭代器实现原理(附带了Java)

前言

只要用过C++的容器,相信大家对迭代器都不会陌生。它提供一种统一的接口形式来遍历相应的容器(例如数组,链表,map等)。

例子1:迭代器的遍历
利用迭代器遍历数组vector

vector<int> vi{ 1, 3, 5, 7, 9 };
for(auto it = vi.begin(); it != vi.end(); ++it) {
    cout<<*it<<endl;
}

利用迭代器遍历链表list

list<int> li{ 2, 4, 6, 8, 10 };
for(auto it = li.begin(); it != li.end(); ++it) {
    cout<<*it<<endl;
}

大家可以看到,上述两端代码唯一进行修改的就是将vector修改为list(vi修改成li仅仅是为了命名的区别),遍历代码没有任何改变,就可以轻易完成底层存储结构从数组到链表的转化,是不是特别棒?

例子2:一个算法的例子,将当前迭代器向前移动off个单位
算法原型:

advance函数:它接收一个迭代器参数,以及一个偏移量_Off,表示将当前的迭代器向前移动_Off个长度(当_Off<0的时候,相当于向后移动)
template<class _InIt, class _Diff> inline
void advance(_InIt& _Where, _Diff _Off)
{   // increment iterator by offset, arbitrary iterators
    _Advance(_Where, _Off, _Iter_cat(_Where));
}

利用advance函数将vector的迭代器it向前移动3个单位

auto it = vi.begin();  //vi = {1, 3, 5, 7, 9}
cout<<*it<<endl; //we get 1

advance(it, 3); 
cout<<*it<<endl; //we get 7

利用advance函数将list的迭代器it向前移动3个单位

auto it = li.begin();  //li = {2, 4, 6, 8, 10}
cout<<*it<<endl; //we get 2

advance(it, 3); 
cout<<*it<<endl; //we get 8

例子1和2形象的展示了迭代器处理的抽象一致性。不用关心底层容器的结构构建,而用一种统一的方式来对容器进行处理,而且在此基础上进一步实现通用一致的算法,岂不妙哉。当需要修改底层容器的结构时候,只需要修改容器的创建代码,其他算法层面基本不需要改动。

有没有感觉到它的美,那么很容易疑问它是如何实现不同容易的统一调用的呢?在回答这个问题之前,我想先简单说一下Java的实现方案(本人只根据个人想法来写,也许与实际的情况有一点点的偏差,不过本人可以担保,思想肯定是这样的,编程学的就是思想!)

Java的实现

首先盗取网上的一张图(本来是C#的,其实Java实现也是这个结构)
图片1-Java迭代器

Aggregate类可以认为是集合抽象类,Iterator是迭代器抽象类。迭代器抽象类包含几个抽象方法(可见名知意)。如果需要实现一个具体容器,首先需要实现抽象容器的方法,还需要实现一个具体迭代器类,它通过具体容器的CreateIterator()函数进行绑定。这样ConcreteIterator就持有了具体容器的this指针,就可以实现相对应的first,next等方法了。

例子3:以java中的数组ArrayList为例,代码来源于AB,原谅我未经同意就盗取了代码,只是为了分享知识。

首先实现具体的容器类ArrayList,它继承一个抽象容器接口Collection,大概形如

public class ArrayList implements Collection {  
    Object[] objects = new Object[10];  
    int index = 0;  

    @Override  
    public void add(Object o) {  
        if(index == objects.length) {  
            Object[] newObjects = new Object[objects.length * 2];  
            System.arraycopy(objects, 0, newObjects, 0, objects.length);  
            objects = newObjects;  
        }  
        objects[index] = o;  
        index ++;  
    }  
    ...
 }

代码中只简单显示了add函数的实现。ArrayList首先内部创建了一个数据objects,当增加新的元素时,先判断数组是否已经满了,如果已满,那么重新分配一个长度*2的数组,将元素复制过去,然后将新增的元素放入到index指向的位置,并将index增加1。其实index就代表是数组中元素的个数。

然后实现它的迭代器类,ArrayList实现了一个内部类,ArrayListIterator,它继承于Iterator抽象接口,并实现相应的接口方法。示例代码如下:

private class ArrayListIterator implements Iterator {  
        private int currentIndex;  
        private int lastRet;           

        public ArrayListIterator(){  
            currentIndex = 0;  
            lastRet = -1;  
        }  

        @Override  
        public boolean hasNext() {  
            if(currentIndex >= index) return false;  
            else return true;  
        }  
        ....
 }

代码中只显示了构造函数ArrayListIterator以及hasNext方法。在ArrayListIterator函数中,设置当前迭代器指向的元素索引位置currentIndex = 0,也就是说起始位置,lastRet不用管。在hasNext中,index变量是ArrayList的数组元素的总个数,currentIndex是迭代器中的变量,代表当前指向的元素索引。它通过判断当前的索引是否到达了元素末尾,来判断是否还有下一个元素。

再通过一个类似于CreateIterator方法将具体容器和具体迭代器进行绑定,ArrayList中使用的方法名为iterator,大概形如:

@Override  
public Iterator iterator() {  
    return new ArrayListIterator();  
}  

其实就是图中的return new ConcreteIterator(this)。你可能的有的疑问就是iterator为什么没有像图中ConcreteIterator描述的那样接受this指针作为参数呢? 这是由于ArrayListIterator类似ArrayList的内部类来实现的。 this指针提供的功能就是使得具体迭代器对象类如ArrayListIterator能够访问具体容器类如ArrayList的内部数据成员,从而达到实现迭代器接口函数first,next这样的功能。而java内部类本身就能够访问外部类的数据成员,从而使得不需要将ArrayList的this指针传递给ArrayListIterator就可以使得能够访问ArrayList。

最后说说demo的使用,说了java的迭代器,相信你已经大概猜到它怎么使用了吧,具体使用的demo代码(引用的他人的代码)。函数名与图片所示有不太一样的地方,但是相信聪明的你是可以分辨出它们的对应关系的。

public static void main(String[] args)
    {
        Collection<String> list=new ArrayList<>();
        list.add("abc");
        list.add("edf");
        list.add("ghi");
        for(Iterator<String> it=list.iterator();it.hasNext();)
        {
            System.out.println(it.next());
        }
    }

首先创建了一个ArrayList对象,并通过add函数增加了几个元素,然后创建ArrayList的迭代器ArrayListIterator,来遍历list中的元素。

C++实现

当你看到这儿,心里是不是一阵mmp:我是来看C++的迭代器实现的,你给我放一个Java实现是个什么鬼,我根本不关心好嘛。 本人来解释一下:一方面Java的实现比较容易理解,通过它作为一个引子可以方便我们理解C++的实现;另一方面Java迭代器的实现框架已经单独成为四人帮design pattern一书中的迭代器设计模式,这应该能够彰显出它的伟大了吧。一个更好的解释就是不要只关心迭代器本身,而多去想想它这种内部设计的内涵以及意图,为什么这样设计,有什么好处,它是否做到了对扩展开放,对修改封闭,是否遵循了编程的准则,很多情况下,想通了背后的设计哲学才是真正的编程功力的提升(也许用升华更好一些)。

C++的猜测实现
有了上面的Java实现,一个显而易见的C++实现应该是这个样子的。vector和list都继承于Collection集合抽象类,然后vector和list的iterator都继承于Iterator抽象基类。 it.end()等价于hasNext()方法,it++等价于next()方法,算法advance的实现直接根据不同的具体迭代器类型执行形如it++的操作就行啦,哇好完美。然而,这并不是C++中迭代器的实现原理。Java实现中唯一的弊端就是,迭代器的函数都是抽象函数,那么在C++中也就是虚函数,虚函数有一个很大的弊端就是函数调用必须在运行期才能够正常识别(想关注虚函数的实现原理的请参见本人早些年发表的博客C)。也就是说需要到程序真正跑起来的时候,才能到得到待调用函数的指针,这个性能损失是不小的。那么如何既能使用Iterator提供的便利性,又能够不牺牲虚函数调用的损失呢?C++利用了强大的迭代器tag标识,并利用模板重载,typename等实现了迭代器类型的编译器决策(而不是像虚函数的运行期决策),有很大的性能优势。

C++的真实实现

首先给大家打一个预防针,下面代码量很多,较为难读。大部分是本人直接从C++的头文件中复制粘贴过来,有部分为了介绍的方便本人进行了简化(但不影响其实际功能)。本人思路是先从第一大节中举例子使用的advance函数入手,通过剖析它的实现来引出iterator_tag的概念,然后分析vector和list的具体实现,以及其tag的关系,进而理通整个流程。

算法advance分析

首先说明一下我们为什么从advance入手,而不是从sort等其他函数入手。这是因为advance函数参数简单,实现简单,且完美呈现了不同种迭代器的统一实现性,只要理解了advance,你就很容易猜测到像sort这种应该如何实现了。

advance源码

template<class _InIt,
class _Diff> inline
void advance(_InIt& _Where, _Diff _Off)
{   // increment iterator by offset, arbitrary iterators
_Advance(_Where, _Off, _Iter_cat(_Where));
}

如上述代码所示,advance函数接收两个参数,迭代器_Where和偏移量_Off,然后进一步调用包装的_Advance函数。现在先透露一下函数_Iter_cat(_Where)是根据传入的迭代器参数来得到迭代器的类型Tag,并用Tag temp来生成一个临时Tag类型的变量,作为参数传递到_Advance。重点:传入的迭代器参数有可能是vector的迭代器,也可能是list的迭代器,还有可能是map的迭代器,因此它们的类型Tag肯定不能是完全一样的,也就是说我们应该有多个_Advance函数,分别针对不同的类型Tag进行不同的处理。

引出迭代器类型Tag

回顾使用容器的经验,如何将一个迭代器it向前移动_Off个单位呢? 如果是数组vector,我们很容易想到应该可以使用 it + _Off直接跳跃过去,对于链表list,我们无法直接跳跃过去,只能使用it++的操作循环_Off次,从而达到这个目的。 另一方面,我们还忽略了_Off<0的情况,也就是说不是向前移动,而是向后移动,这个时候如果是数组,可以使用it-_Off实现,对于双向链表可以通过多次it–达到,但是对于单向只能向前移动的迭代器,_Off<0就不应该被支持。

也就是说我们需要有多种不同的迭代器类型,有些类型支持随机存取,有些类型不能够随机,只能够前进,有些类型前进和后退都可,是双向的,因此我们有

// ITERATOR TAGS (from <iterator>)
struct input_iterator_tag
{   // identifying tag for input iterators
};

struct _Mutable_iterator_tag
{   // identifying tag for mutable iterators
};

struct output_iterator_tag
: _Mutable_iterator_tag
{   // identifying tag for output iterators
};

struct forward_iterator_tag
: input_iterator_tag, _Mutable_iterator_tag
{   // identifying tag for forward iterators
};

struct bidirectional_iterator_tag
: forward_iterator_tag
{   // identifying tag for bidirectional iterators
};

struct random_access_iterator_tag
: bidirectional_iterator_tag
{   // identifying tag for random-access iterators
};

看到没有,每一个struct类都是一个迭代器类型tag。 它最特别的地方就是具有空的数据成员。用这些tag类创建的对象所占用的存储空间为1(使用sizeof测试)。像vector支持随机访问,因此我们可以猜测vector类的迭代器所属于的tag类型为random_access_iterator_tag,而list是双向链表,不支持随机访问,因此list所隶属的tag类型为bidirectional_iterator_tag。

有了这几种不同的迭代器tag类型,我们的_Advance函数就应该重载这几种类型,并给出不同的实现。由于篇幅关系,只列出前向迭代器forward,双向迭代器bidirectional,以及随机访问迭代器random_access的实现。

// TEMPLATE FUNCTION advance
template<class _FwdIt,
class _Diff> inline
void _Advance(_FwdIt& _Where, _Diff _Off, forward_iterator_tag)
{   // increment iterator by offset, forward iterators
    for (; 0 < _Off; --_Off)
        ++_Where;
}

template<class _BidIt,
class _Diff> inline
void _Advance(_BidIt& _Where, _Diff _Off, bidirectional_iterator_tag)
{   // increment iterator by offset, bidirectional iterators
for (; 0 < _Off; --_Off)
    ++_Where;
for (; _Off < 0; ++_Off)
    --_Where;
}

template<class _RanIt,
class _Diff> inline
void _Advance(_RanIt& _Where, _Diff _Off, random_access_iterator_tag)
{   // increment iterator by offset, random-access iterators
_Where += _Off;
}

可以看出对于前向迭代器来说,只有在_Off大于0,才会移动迭代器向前行进_Off个单位;双向迭代器在大于0和小于0时,都可以移动;随机访问迭代器支持大跃进,直接从当前位置跳跃到目标位置(不需要循环_Off次)。

总结:我们简单总结一下当前的结果,算法advance函数对外提供统一的接口,接收迭代器和offset作为参数,在advance函数中,利用一个特别的函数_Iter_cat(_Where)来获得当前迭代器类型tag,并创建了一个temp对象,然后转向调用_Advance函数。我们利用函数重载了多个_Advance实现,以第三个参数(迭代器tag类型的temp)进行区别多个同名函数,从而达到传入不同的迭代器调用不同的_Advance函数的目的。

获取迭代器tag的实现

我们刚才忽略了_Iter_cat(_Where)的实现,只简单说明了它是根据传入的_Where迭代器,得到它隶属的迭代器类型tag,然后创建了一个此类型的temp(大小为一个字节),并返回temp。下面我们看一下它的实现:

// TEMPLATE FUNCTION _Iter_cat
template<class _Iter> inline
typename iterator_traits<_Iter>::iterator_category
    _Iter_cat(const _Iter&)
{   // return category from iterator argument
typename iterator_traits<_Iter>::iterator_category _Cat;
return (_Cat);
}

如上面代码所示,传入参数为_Iter类型的迭代器,返回值为_Cat。_Cat就是我们说的temp变量,它的类型就是传入的迭代器_Iter的tag类型,也就是iterator_traits<_Iter>::iterator_category。 前面的typename关键字就是说明它后面的一大串是个类型而已,可简单忽略它。那现在问题来了,如何根据传入的迭代器_Iter类型,确定它的tag呢?依我之见,直接给不同的迭代器类中增加一个typedef,例如如下的代码:

class VectorIterator
{
    typedef random_access_iterator_tag iterator_category;
};
class ListIterator
{
    typedef bidirectional_iterator_tag iterator_category;
};

这样我们的_Iter_cat的实现就可以简单如下,我们将它命名为想当然版本

// TEMPLATE FUNCTION _Iter_cat
template<class _Iter> inline
typename _Iter::iterator_category
    _Iter_cat(const _Iter&)
{   // return category from iterator argument
typename _Iter::iterator_category _Cat;
return (_Cat);
}

按道理来说这种简单的方式是可行的,那为什么还要去绕一个大圈用了iterator_traits<_Iter>::iterator_category这种东西来得到给定迭代器的tag类型。先不管它为什么这样做吧,我们先看它是如何达到这个效果的,我们称它为标准版本

// TEMPLATE CLASS iterator_traits
template<class _Iter>
    struct iterator_traits
    {   // get traits from iterator _Iter
    typedef typename _Iter::iterator_category iterator_category; //here it is the key
    typedef typename _Iter::value_type value_type;
    typedef typename _Iter::difference_type difference_type;
    typedef difference_type distance_type;  // retained
    typedef typename _Iter::pointer pointer;
    typedef typename _Iter::reference reference;
};

它的实现很简单,依然是一个空类(包含一堆类型)。因此上述的iterator_traits<_Iter>::iterator_category代码,其实就是绕了一圈依然对应着typename _Iter::iterator_category这一段代码(typedef重命名的一下而已),回到我们的问题,为何不直接使用 _Iter::iterator_category呢?

iterator_traits类之美

让我们来看下面的一段代码:

int ai[] = {3, 6, 9, 12, 15};
int *p = ai;
advance(p, 3);
cout <<"*p="<< *p << endl; //we get *p=12

可以看出advance不仅能够对像vector,list这样的容器处理,也可以对传统的数据指针作处理。而我们知道传统的int*这种类型,是没有iterator_category的,因此如果我们使用想当然版本的_Iter_cat(p),它会执行typename int*::iterator_category _Cat,毫无疑问这句话会失败,因为压根int*就不是一个类类型,何谈取它的iterator_category呢。我们标准库设计的人员创造性的提出了标准版本的_Iter_cat(p),它绕了远路,但是将类型tag析取的过程转交给了iterator_traits类,由它通过iterator_traits<_Iter>::iterator_category 来获取_Iter的类型。现在我们有了iterator_traits类的辅助,我们就可以针对int*这种特别的类型设计它的特化版本(特化一词在模板实现中非常普遍,当我们设计了general版本之后,又想对某些特别的进行特殊处理,这个时候我们会使用特化),也就是说我们的iterator_traits类对常见的迭代器类,直接使用迭代器类的iterator_category,而对于非迭代器类int*这种,设计了特化版本的实现来返回它的迭代器tag。 根据我们的经验,int* p这种指针类型支持随机存储,也就是说p+3,p+10都是可以的,因此我们针对普通指针的特化版本如下:

template<class _Ty> //_Ty can be int, double...
    struct iterator_traits<_Ty *>
    {   // get traits from pointer
    typedef random_access_iterator_tag iterator_category;
    typedef _Ty value_type;
    typedef ptrdiff_t difference_type;
    typedef ptrdiff_t distance_type;    // retained
    typedef _Ty *pointer;
    typedef _Ty& reference;
    };

可以看出我们对于普通的指针类型定义了特化的tag,也就是我们前面分析的random_access_iterator_tag。

总结:我们首先分析了advance的实现,它根据传入的迭代器获取到它的类型tag(空类),然后创建了tag类型的对象temp,并将此temp和原有的迭代器参数和offset参数共同传入到_Advance实现中。由于temp可能是多种不同的迭代器tag的对象,因此会有多种不同的实现,我们简单讨论了单向迭代器,双向迭代器,以及随机访问迭代器的_Advance实现。而后,我们将主要精力放在了如何根据传入的迭代器_Iter得到它的类型tag的,我们首先提出了一种简单的方案,直接在_Iter迭代器类中定义它所属的tag类型,例如vector迭代器定义random_access的tag,而list迭代器定义bidirectional的tag,然后直接通过_Iter::iterator_category获取它所属的tag。这种方案简单,表面看起来没有问题,但是当我们的advance函数需要处理原生数组int*这种类型的时候,它却无能无力了,因为这个时候_Iter就是int*,它没有自己的iterator_category。对于这种情况,标准库设计者们利用了委托方式,将tag析取的过程交给了iterator_traits类做处理,对于容器类的迭代器,利用已经设定好的iterator_category,对于原生的数组如int*,通过对iterator_traits类的特化,实现了它的特化版本,从而使得原生数组也有自己的迭代器tag类型random_access。

vector和list的迭代器实现

vector的迭代器类,设置自己的iterator_category为随机访问tag类型

vector的迭代器
template<class _Myvec>
    class _Vector_iterator
        : public _Vector_const_iterator<_Myvec>
    {   // iterator for mutable vector
public:
    typedef _Vector_iterator<_Myvec> _Myiter;
    typedef _Vector_const_iterator<_Myvec> _Mybase;
    typedef random_access_iterator_tag iterator_category;//random tag

    typedef typename _Myvec::value_type value_type;
    typedef typename _Myvec::difference_type difference_type;
    typedef typename _Myvec::pointer pointer;
    typedef typename _Myvec::reference reference;
    ...
}

vector的基类_Vector_val里面定义了它的iterator为上面定义的_Vector_iterator。因此当我们使用vector::iterator的时候,实际上先到达_Vector_val<_Myt>::iterator,然后到达_Vector_iterator<_Myt>,也就是最终迭代器。

// TEMPLATE CLASS _Vector_val
template<class _Val_types>
    class _Vector_val
        : public _Container_base
    {   // base class for vector to hold data
public:
    typedef _Vector_val<_Val_types> _Myt;

    typedef typename _Val_types::value_type value_type;
    typedef typename _Val_types::size_type size_type;
    typedef typename _Val_types::difference_type difference_type;
    typedef typename _Val_types::pointer pointer;
    typedef typename _Val_types::const_pointer const_pointer;
    typedef typename _Val_types::reference reference;
    typedef typename _Val_types::const_reference const_reference;

    typedef _Vector_iterator<_Myt> iterator; //iterator
    typedef _Vector_const_iterator<_Myt> const_iterator;

    _Vector_val()
        {   // initialize values
        _Myfirst = pointer();
        _Mylast = pointer();
        _Myend = pointer();
        }

    pointer _Myfirst;   // pointer to beginning of array
    pointer _Mylast;    // pointer to current end of sequence
    pointer _Myend; // pointer to end of array
    };

list类的迭代器类,设置自己的iterator_category为双向访问tag类型

template<class _Mylist>
    class _List_iterator
        : public _List_const_iterator<_Mylist>
    {   // iterator for mutable list
public:
    typedef _List_iterator<_Mylist> _Myiter;
    typedef _List_const_iterator<_Mylist> _Mybase;
    typedef bidirectional_iterator_tag iterator_category; //bidirectional tag

    typedef typename _Mylist::_Nodeptr _Nodeptr;
    typedef typename _Mylist::value_type value_type;
    typedef typename _Mylist::difference_type difference_type;
    typedef typename _Mylist::pointer pointer;
    typedef typename _Mylist::reference reference;
    ....
}

list的基类_List_val里面定义了它的iterator为上面定义的_List_iterator。因此当我们使用list::iterator的时候,实际上先到达_List_val<_Myt>::iterator,然后到达_List_iterator<_Myt>,也就是最终迭代器。

// TEMPLATE CLASS _List_val
template<class _Val_types>
    class _List_val
        : public _Container_base
    {   // base class for list to hold data
public:
    typedef _List_val<_Val_types> _Myt;

    typedef typename _Val_types::_Nodeptr _Nodeptr;
    typedef _Nodeptr& _Nodepref;

    typedef typename _Val_types::value_type value_type;
    typedef typename _Val_types::size_type size_type;
    typedef typename _Val_types::difference_type difference_type;
    typedef typename _Val_types::pointer pointer;
    typedef typename _Val_types::const_pointer const_pointer;
    typedef typename _Val_types::reference reference;
    typedef typename _Val_types::const_reference const_reference;

    typedef _List_const_iterator<_Myt> const_iterator;
    typedef _List_iterator<_Myt> iterator; //iterator
    ...
}

好了,分析了昨天一晚上,写了4个多小时。本来还想将它如何alloc给分析一下(class _Alloc = allocator<_Ty>),奈何脑袋都有点大了,好晕。暂时就先到这里了,大家如果看了这篇文章感觉有些收获,还想关注一些其他的我还未写的内容,不妨发布评论,我有空会再分析。

猜你喜欢

转载自blog.csdn.net/lipeng08/article/details/80281769