STL之迭代器(Iterator)

目錄


一、迭代器是什麼?

1.1、本質

迭代器本質上是什麼?當然,任何一種特定的迭代器都是某種類型的對象。不過,迭代器的類型非常多,畢竟每個迭代器都是與某個特定容器類型相關聯的。它需要保存一些必要的信息,以便我們對容器執行某些特定的任務。因此,有多少種容器就有多少種迭代器,有多少種特殊要求就有多少種迭代器。

1.2、作用

STL的中心思想在於:將數據容器和算法分開,彼此獨立設計,最後再以一帖膠着劑將它們撮合在一起。這個膠着劑就是迭代器。反過來,也可以說迭代器機制是爲了最小化算法與所操作的數據結構間的依賴性:
在這裏插入圖片描述

1.3、迭代器與指針

迭代器是一種行爲類似指針的對象,迭代器提供了間接訪問的操作(如解引用操作*)和移動到新元素的操作(例如,++操作移動到下一個元素)。

迭代器並未實現爲指針,它們是所謂的“泛型指針”。你甚至可以反過來思考它,由於迭代器是泛型的,這意味着指針是迭代器,在任何可以使用迭代器的地方,都可以使用指針。1

從實現的角度來看,迭代器是一種將operator*、operator->、operator++、operator-- 等指針相關操作予以重載的class template。

1.4、迭代器類別

標準庫提供了5種迭代器(5個迭代器類別,Iterator category):

  • 輸入迭代器(input Iterator):只用於順序訪問,只讀。
  • 輸出迭代器(output Iterator):順序訪問,只寫。
  • 前向迭代器(forward iterator):只能在序列中沿一個方向移動,可以讀寫元素。
  • 雙向迭代器(bidirectional Iterator):可以正向/反向移動,可讀寫元素。
  • 隨機訪問迭代器(random-access iterator):提供在常量時間內訪問序列中任意元素的能力。

1.4.1、5種迭代器的關係

在這裏插入圖片描述
圖中直線與箭頭代表的並非C++的繼承關係,而是所謂concept(概念)與refinement(強化)的關係。

二、Traits編程技法

2.1、引子

在算法中運用迭代器時,很可能會用到其相應類型。什麼是相應類型?迭代器所指之物的類型便是其一。假設算法中有必要聲明一個變量,以“迭代器所指對象的類型”爲類型,如何是好?畢竟C++只支持sizeof(),並未支持typeof()!即便動用RTTI性質中的typeid(),獲得的也只是類型名稱,不能拿來做變量聲明之用。

解決辦法是:利用function template的參數推導機制。如:
在這裏插入圖片描述
以func()爲對外接口,卻把實際操作全部置於func_impl()之中。由於func_impl()是一個function template,一旦被調用,編譯器會自動進行template參數推導,於是導出類型T,順利解決了問題。

迭代器相應類型不只是"迭代器所指對象的類型"一種而已。根據經驗,最常用的相應類型有五種:value_type、differencee_type、pointer、reference、Iterator_category,並非任何情況下任何一種都可以利用上述的template參數推導機制來取得。

2.2、Traits編程技法

上述參數類型推導技巧雖然可以用於value_type,卻非全面可用:萬一value_type必須用於函數的返回值,就束手無策了,畢竟函數的"template參數推導機制"推而導之的只是參數,無法推導函數的返回值類型。2

聲明內嵌類型似乎是個好主意,像這樣:
在這裏插入圖片描述
看起來不錯,但是有個隱晦的陷阱:並不是所有迭代器都是class type。原生指針就不是。如果不是class type,就無法爲它定義內嵌類型。但STL絕對必須接受原生指針作爲一種迭代器,所以上面這樣還不夠。

爲了讓上述一般化概念針對特定情況(例如針對原生指針)做特殊化處理,可以使用模板部分特例化(template partial specialization)

先前的問題是,原生指針並非class,因此無法爲它們定義內嵌類型。現在,我們可以針對“迭代器之template參數爲指針”者,設計特例化版本的迭代器。

下面這個class template專門用來“萃取”迭代器的特性,而value_type正是迭代器的特性之一:
在這裏插入圖片描述
這個所謂的traits,其意義是,如果 I 定義有自己的value_type,那麼通過這個traits的作用,萃取出來的value_type就是 I::value_type。換句話說,如果 I 定義有自己的value_type,先前那個func()可以改寫成這樣:
在這裏插入圖片描述
除了多一層間接性,帶來的好處是什麼呢?好處是traits可以擁有特例化版本。現在,我們令iterator_traits擁有一個partial specialization如下:
在這裏插入圖片描述
於是,原生指針int*雖然不是一種class type,亦可以通過traits取其value_type。這就解決了先前的問題。

針對“指向常量對象的指針(pointer to const)”,設計的特例化版本如下:
在這裏插入圖片描述
traits扮演的“特性萃取機”,萃取各個迭代器的特性,這裏所謂的迭代器特性,指的是迭代器的相應類型。當然,若要這個“特性萃取機”traits能夠有效運作,每一個迭代器必須遵循約定,自行以內嵌類型定義的方式定義出相應類型。
在這裏插入圖片描述

三、迭代器相應類型

本節只介紹iterator_category,其他迭代器相應類型的具體實現,請參看下一節四、具體迭代器示例

共有5種迭代器,詳情請參考1.4、迭代器類別。設計算法時,如果可能,我們應儘量針對某種迭代器提供一個明確定義,並針對更強化的某種迭代器提供另一種定義,這樣才能在不同情況下提供最大效率。

爲了區別不同的迭代器,traits中定義了5個類,代表5種迭代器類型:
文件位置:libstdc++ -v3\include\bits\stl_iterator_base_types.h

  /**
   *  @defgroup iterator_tags Iterator Tags
   *  These are empty types, used to distinguish different iterators.  The
   *  distinction is not made by what they contain, but simply by what they
   *  are.  Different underlying algorithms can then be used based on the
   *  different operations supported by different iterator types.
  */
  //@{ 
  ///  Marking input iterators.
  struct input_iterator_tag { };

  ///  Marking output iterators.
  struct output_iterator_tag { };

  /// Forward iterators support a superset of input iterator operations.
  struct forward_iterator_tag : public input_iterator_tag { };

  /// Bidirectional iterators support a superset of forward iterator
  /// operations.
  struct bidirectional_iterator_tag : public forward_iterator_tag { };

  /// Random-access iterators support a superset of bidirectional
  /// iterator operations.
  struct random_access_iterator_tag : public bidirectional_iterator_tag { };
  //@}

四、具體迭代器示例

4.1、__iterator_traits

文件位置:libstdc++ -v3\include\bits\stl_iterator_base_types.h

#if __cplusplus >= 201103L
  // _GLIBCXX_RESOLVE_LIB_DEFECTS
  // 2408. SFINAE-friendly common_type/iterator_traits is missing in C++14
  template<typename _Iterator, typename = __void_t<>>
    struct __iterator_traits { };

  template<typename _Iterator>
    struct __iterator_traits<_Iterator,
			     __void_t<typename _Iterator::iterator_category,
				      typename _Iterator::value_type,
				      typename _Iterator::difference_type,
				      typename _Iterator::pointer,
				      typename _Iterator::reference>>
    {
      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;
    };

  template<typename _Iterator>
    struct iterator_traits
    : public __iterator_traits<_Iterator> { };
#else
  template<typename _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;
    };
#endif

  /// Partial specialization for pointer types.
  template<typename _Tp>
    struct iterator_traits<_Tp*>
    {
      typedef random_access_iterator_tag iterator_category;
      typedef _Tp                         value_type;
      typedef ptrdiff_t                   difference_type;
      typedef _Tp*                        pointer;
      typedef _Tp&                        reference;
    };

  /// Partial specialization for const pointer types.
  template<typename _Tp>
    struct iterator_traits<const _Tp*>
    {
      typedef random_access_iterator_tag iterator_category;
      typedef _Tp                         value_type;
      typedef ptrdiff_t                   difference_type;
      typedef const _Tp*                  pointer;
      typedef const _Tp&                  reference;
    };

4.2、iterator

爲了符合規範,任何迭代器都應該提供5個內嵌相應類型,以利於traits萃取,否則便是自別於整個STL架構,可能無法與其他STL組件順利搭配。因此,STL提供了一個iterator class,如果每個新設計的迭代器都繼承自它,就可以保證符合STL所需規範:

 /**
   *  @brief  Common %iterator class.
   *
   *  This class does nothing but define nested typedefs.  %Iterator classes
   *  can inherit from this class to save some work.  The typedefs are then
   *  used in specializations and overloading.
   *
   *  In particular, there are no default implementations of requirements
   *  such as @c operator++ and the like.  (How could there be?)
  */
  template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,
           typename _Pointer = _Tp*, typename _Reference = _Tp&>
    struct iterator
    {
      /// One of the @link iterator_tags tag types@endlink.
      typedef _Category  iterator_category;
      /// The type "pointed to" by the iterator.
      typedef _Tp        value_type;
      /// Distance between iterators is represented as this type.
      typedef _Distance  difference_type;
      /// This type represents a pointer-to-value_type.
      typedef _Pointer   pointer;
      /// This type represents a reference-to-value_type.
      typedef _Reference reference;
    };

五、迭代器、容器和算法

設計適當的相應類型是迭代器的責任,設計適當的迭代器則容器的責任。只有容器本身,才知道該設計出怎樣的迭代器來遍歷自己,並執行迭代器該有的各種行爲(前進、後退、取值…)。至於算法,完全可以獨立於容器和迭代器之外自行發展,只要設計時以迭代器爲對外接口就行。


  1. https://gcc.gnu.org/onlinedocs/libstdc++/manual/iterators.html ↩︎

  2. 可以使用尾置返回類型和decltype()。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章