1. 程式人生 > >C++迭代器實現原理(附帶了Java)

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>),奈何腦袋都有點大了,好暈。暫時就先到這裡了,大家如果看了這篇文章感覺有些收穫,還想關注一些其他的我還未寫的內容,不妨釋出評論,我有空會再分析。