1. 程式人生 > >迭代器概念及traits程式設計技法.md

迭代器概念及traits程式設計技法.md

前言

迭代器作為一個抽象概念,在程式設計中並沒有直接對應於這個概念的實物。在設計模式中,iterator模式定義為:提供一種方法,能使之依序巡防某個容器所含元素,而無須暴露該容器的內部表達方式。

3.1 迭代器設計思維–STL關鍵所在

STL的核心思想:將容器和演算法分離,彼此獨立設計,然後用迭代器將兩者撮合。

3.2 迭代器是一種智慧指標

指標最常見的行為是內容提領(derefence)和成員訪問(member acess),因此迭代器程式設計的工作就是要對operator*和operator->進行過載。

3.3 迭代器所指的型別

如果在演算法中要宣告一個變數,其型別為迭代器所指的型別,應該如果做呢?

利用fuction template的引數推導機制
template<class I, class T>
void func_impl(I iter, T t)
{
    T tmp;      //T就是迭代器所指之物

    //...這裡做func()應該做的事情    

}

template<I iter>
inline void func(I iter)
{
    func_impl(iter, *iter); //func的全部工作遷移到func_impl
}

int main()
{
    int i;
    func(&i);
}

我們以func()為對外介面,實際操作放在func_impl中,由於其為一個function template,一旦被呼叫,編譯器會自動進行引數推導,於是知道了型別T,解決問題。

Traits程式設計技法–STL原始碼門鑰

迭代器所指物件的型別稱為迭代器的value type, 如果用於函式的返回值,上述方法不能使用。

宣告內嵌型別

template <class T>
class MyIter{
public:
    typedef T value_type;
    T *ptr;
    MyIter(T *p = 0):ptr(p){}
    T& operator*()const{return *ptr;}
};

利用typedef T value_type把模版中的型別T暴露出來,我們可以通過以下的方法讓函式的返回值為T的型別.

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

其中模版I必須提高value_type變數的自定義迭代器,編譯器在編譯階段,會通過MyIter的實際模板值推匯出func函式的返回值型別應該是什麼,這樣func函式的返回值就可以隨著我們自定義迭代器傳入的模板引數的改變而改變。
整體實現程式碼為:

#include <iostream>

using namespace std;

template <class T>
class MyIter
{
public:
    typedef T value_type;
    T *ptr;
    MyIter(T *p = NULL) : ptr(p){}
    T& operator*() const
    {
        return *ptr;
    }
};

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

int main()
{
    MyIter<int> myIter(new int(1));
    cout << func(myIter) << endl;       //1

    return 0
}

看起來很不錯,但是不是所有的迭代器都是class type,是對於原生指標就會有問題,因為原生指標是沒有內嵌型別的。

利用模板的特化的特性,在中間增加一層Traits類,讓我們設計的func函式既支援自定義的迭代器又支援通用指標。

模版偏特化:如果一個class template擁有一個以上的template引數,我們可以針對其中某個(或數個,並非全部)的template引數進行特化工作。

/*
1、模版的特化
對於一個模版,對其中的所有模版引數指定確定的型別。
2、偏特化
對於一個模版,部分的模版引數指定確定的型別
3、在進行模版例項化的時候,編譯器會對特定的型別找到最合適,最匹配的實現。
*/
#include <iostream>
using namespace std;
//模版
template<class T1, class T2>
class Test
{
public:
    Test (T1 i, T2 j): a(i),b(j)
    {
        cout << "使用原模版:" << a << " " << b << endl;
    }
private:
    T1 a;
    T2 b;
};
//全特化
template<>
class Test<int , char>
{
public:
    Test(int i, char j) : a(i), b(j)
    {
        cout << "使用全特化:" << a << " " << b << endl;
    }
private:
    int a;
    char b;
};
//偏特化
template<class T1>
class Test<T1, char>
{
public:
    Test(T1 i, char j) : a(i), b(j)
    {
        cout << "使用偏特化:" << a << " " << b << endl;
    }
private:
    T1 a;
    char b;
};
int main()
{
    Test<double, int> t1(2.22, 3);
    Test<double, char> t2(2.22, 'c');
    Test<int, char> t3(3, 'b');
    return 0;
}
/*
使用原模版:2.22 3
使用偏特化:2.22 c
使用全特化:3 b
*/  

明白了偏特化的意義,我們可以針對“迭代器之template引數為指標”者設計特化版的迭代器。

(1) 首先設計一個class template專門來萃取迭代器的特性

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

這個traits的意義是,如果I定義有自己的value_type,那麼通過這個traits的作用,萃取出來的value_type就是I::value_type。
(2) 然後使用偏特化解決原生指標和指向常數物件的原生指標

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

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

整體的程式碼如下:

#include <iostream>

using namespace std;

//自定義迭代器
template<class T>
struct MyIter
{
    typedef T value_type;
    T *ptr;

    MyIter(T* p = NULL):ptr(p)
    {

    }

    T& operator*() const 
    {
        return *ptr;
    }
};

//Traits程式設計技法
//支援自定義迭代器
template<class T>
class Traits
{
public:
    typedef typename T::value_type value_type;
};

//特化,支援傳統通用指標
template <class T>
class Traits<T*>
{
public:
    typedef T value_type;
};

//特化,支援傳統的const指標
template<class T>
class Traits<const T*>
{
public:
    typedef T value_type;
};

//模版函式
template <class I>
typename Traits<I>::value_type func(I iter)
{
    return *iter;
}

//測試
int main(int argc, char** argv)
{
    MyIter<int> p(new int(1));
    const char* ptr = "abc";
    int *a = new int(9);
    cout << func(p) << endl;
    cout << func(a) << endl;
    cout << func(ptr) << endl;

    return 0;
}

那麼現在我們擁有三種版本:
1. 泛型版本
2. 原生指標
3. pointer-to-const
這樣無論是迭代器 Iter 還是 原生指標 int* 還是 const int * 我們都可以通過traits取出正確的value_type。

最常用的迭代器型別有五種
1. value_type:迭代器所指物件的型別
2. difference_type:兩個迭代器之間的距離。也可以用來表示一個容器的最大容量。如果泛型演算法提供計數功能,就必須使用迭代器的這個型別做返回值。針對原生指標,使用ptrdiff_t作為其diffrence_type型別
3. reference_type: 引用型別。從迭代器所指的內容是否允許改變來看,可以將迭代器分為兩種:不允許改變所指之物的內容,稱為constant iterator;可以改變所指之物的內容,稱為 mutable iterator。他們對應的例子分別是 cosnt int *int *。當我們對 mutable iterator (int *) 進行解引用操作時,獲得的不應該是一個右值(rvalue),應該是一個左值(lvalue),因為右值不允許賦值操作(assignment),左值才允許.
4. pointer_type:指標型別
5. iterator_category:迭代器型別, 以下具體討論。

迭代器被分為以下五類:
只讀,只寫,前向,雙向,隨機存取。
* Input Iterator:這種迭代器所指的物件,不允許外界改變;只讀(Read Only)
* Output Iterator:這種迭代器所指的物件,只可以寫入;唯寫(Write Only)
* Forward Iterator:允許“寫入型”演算法在此種迭代器形成的區間上讀寫操作。
* Bidirectional Iterator:可雙向移動。
* Random Access Iterator:涵蓋了所有的算數能力,比如p+n,p-n,p[n],p1-p2,p1

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

這裡的 struct 只作為標記使用,所以不需要任何成員。
然後設計__advance()函式:

template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
  while (n--) ++i;
}
template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n, 
                      bidirectional_iterator_tag) {
  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) {
  i += n;
}

這裡第三個引數只作為標記使用,函式內並沒有使用它們。
然後接下來是 advance 函式:

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
  __advance(i, n, iterator_category(i));
}

原始碼閱讀:

//迭代器traits
template <class 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;
};
//針對原生指標設計的偏特化版本
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;
};

template <class T>
struct iterator_traits<const T*> {
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef const T*                   pointer;
  typedef const T&                   reference;
};
//返回迭代器的型別
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
  typedef typename iterator_traits<Iterator>::iterator_category category;
  return category();
}
//返回迭代器的distance_type
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&) {
  return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}
//返回迭代器的value type
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&) {
  return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

__type_traits

Iterator_traits負責萃取迭代器的特性,__type_traits則負責萃取型別的特性。對於型別的特性,我們關注的點可能在於對該型別是否需要複雜處理。如果答案是否定的,我們在對這些型別進行構造、析構、拷貝賦值等操作時,就可以採取最有效率的措施,比如不使用解構函式,直接free等。
__type_traits提供了一種機制,允許針對不同的型別屬性,在編譯時期完成函式派送決定。這對於撰寫template很有幫助。例如當我們對一個型別未知的陣列進行copy時,如果我們事先知道該元素型別的建構函式是否是不重要的,我們可能可以使用memcpy或是memmove等函式快速處理