STL源碼剖析iterators與traits

iterator概述

迭代器是一種抽象的設計概念。《Design Patterns》中對iterator的定義如下:提供了一種方法,使之能夠依序巡防某個聚合物(容

器)所含的各個元素,而又無需暴露聚合物的內部表述方法。

迭代器最重要的編程工作是對operator*和operator->進行重載

迭代器的相應型別(associated types)

在算法中運用迭代器的時候,可能會使用到迭代器的相應型別。迭代器所指對象的型別就是其一。

C++只支持sizeof(),不支持typeof()。即便動用RTTI性質中的typeid(),獲得的也只是型別名稱,不能拿來做變量聲明之用

解決辦法:

利用function template的參數推導機制

template <class I,class T>
void func_impl(I iter,T t)
{
    T tmp;//T 就是迭代器所指之物
    ...
    //這裏做func()原本要做的原本工作
}
template<class I>
inline
void func(I iter)
{
    func_impl(iter,*iter);//func的全部工作移往func_impl
}
int main()
{
    int i;
    func(&i);
}

我們使用功能func()爲對外接口,但是實際工作全部在func_impl中來實現。

func_impl一旦被調用,編譯器會自動進行template參數推導。於是導出型別T。

最常用的相應型別爲五種,並不是任何情況下任何一種都可以利用上述的template參數推導機制來獲取。我們需要更加全面的解法

爲了使容器和STL水乳交融,一定要爲容器的迭代器定義以下五種型別。

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

上述模板參數型別推導雖然可以用於value_type,卻非全面可用:當value_type必須作爲函數的傳回值,就束手無策了。

因爲template參數推導機制只能獲取參數,無法推導函數的返回值類型。

於是STL中採用內嵌型別的方法:

template<typename T>  
class Iterator  
{  
public:  
    typedef T value_type;//內嵌類型聲明  
    Iterator(T *p = 0) : m_ptr(p) {}  
    T& operator*() const { return *m_ptr;}  
    //...  
  
private:  
    T *m_ptr;  
};  
  
template<typename Iterator>  
typename Iterator::value_type  //以迭代器所指對象的類型作爲返回類型,長度有點嚇人!!!  
func(Iterator iter)  
{  
    return *iter;  
}  
  
int main(int argc, const char *argv[])  
{  
    Iterator<int> iter(new int(10));  
    cout<<func(iter)<<endl;  //輸出:10  
}  

函數func()的返回類型前面必須加上關鍵詞typename。因爲T是一個template參數,編譯器在編譯實例化func之前,對T一無所知,就是說,編譯器並不知道Iterator<T>::value_type是一個類型,或者是一個靜態成員函數,還是一個靜態數據成員,關鍵詞typename的作用在於告訴編譯器這是一個類型,這樣才能順利通過編譯。

但是這裏又有一個隱晦的陷阱:並不是所有迭代器都是類類型class type。原生指針是一種迭代器,就不是類類型。


偏特化Partial Specialization

template<typename U,typaname V,typename T>
class c{...};

partial specialization字面的意思容易誤導我們以爲,偏特化是對template的參數U,V,T指定某個參數值。

正確的理解是,針對template參數更進一步的條件限制所設計出來的一個特化版本。

當泛化版本爲

template<typename T>
class C{...};

那麼特化版本就可以設置爲

template<typename T>
class C<T*> {...};

於是,我們利用偏特化版本來解決迭代器的template參數爲指針的情況。

於是STL中設置一個模板類,也就是萃取類,來實現這一功能

template<typename I>  //泛化版本
struct iterator_traits  
{  
    typedef typename I::value_type value_type;  
}; 

template<typename T>  //偏特化版本  原生指針
struct iterator_traits<T*>  
{  
    typedef T value_type;  
}; 

template<typename T>  //偏特化版本  原生const指針
struct iterator_traits<const T*>  
{  
    typedef T value_type;  
}; 

因此,現在迭代器萃取的任務得到解決。無論是類類型迭代器,還是原生指針int*或者const int* 都可以得到正確的value_type。

這裏需要注意:

容器的迭代器是容器開發者進行設定的。而特性萃取劑iterator_traits是STL指定的。

因此,你的迭代器進行設計的時候一定要定義前述的五種相應型別。

相應型別一:value_type

        value_type就是指迭代器所指對象的類型,例如,原生指針也是一種迭代器,對於原生指針int*,int即爲指針所指對象的類型,也就是所謂的value_type。

相應型別二:difference type

difference_type用來表示兩個迭代器之間的距離

對於原生指針,STL以C++內建的ptrdiff_t作爲原生指針的difference_type。

相應型別三:reference type

reference_type是指迭代器所指對象的類型的引用,reference_type一般用在迭代器的*運算符重載上,如果value_type是T,那麼對應的reference_type就是T&;如果value_type是const T,那麼對應的reference_type就是const T&。

相應型別四:pointer type

pointer就是指迭代器所指的對象,也就是相應的指針,對於指針來說,最常用的功能就是operator*和operator->兩個運算符。因此,迭代器需要對這兩個運算符進行相應的重載工作:

T& operator*() const { return *ptr; } // T& is reference type  
T* operator->() const { return ptr; } // T* is pointer type  

相應型別五:iterator_category

iterator_category的作用是標識迭代器的移動特性和可以對迭代器執行的操作,從iterator_category上,可將迭代器分爲Input Iterator、Output Iterator、Forward Iterator、Bidirectional Iterator、Random Access Iterator五類,具體爲什麼要這樣分類,簡單來說,就是爲了儘可能地提高效率,這也是STL的宗旨之一。

Input Iterator:這種迭代器所指對象不允許外界改變。

Output Iterator:只能寫,不能讀。

Forward Iterator:可讀,可寫

Bidirectional Iterator:可以雙向移動

RandomAccess Iterator:前四種只提供了一部分指針運算能力。前三種支持operator++,第四種還支持operator--,第五種涵蓋所有指針計算能力,包括p+n,p-n,p[n],p1-p2,p1+p2。

迭代器的分類和從屬關係

迭代器型別的巧用和重載

在STL中,效率是一直需要考慮的,當需要一個Input Iterator的時候,傳入一個RandomAccess Iterator肯定不會接受,雖然可用但不是最佳。

一個算法可能接受多種迭代器,爲了效率需要確定那種迭代器後再執行對應的函數。

爲了避免能夠在編譯的時候就選擇正確的版本,重載函數機制可以達到這個目標。所以需要通過迭代器的類型來實現對算法函數的重載。那麼需要該型別是class type而不是數值號碼類的東西,因爲編譯器需要依賴它來進行重載決議(overloaded resolution)

//五種迭代器型別
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 {};

這些classes只作爲標記用,所以不需要任何成員。

以advance算法爲例:

需要注意這裏的forward iterator是進行了單純的傳遞調用,後面會講如何消除傳遞調用。

//InputIterator類型定義
template<class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
    while (n--) ++i;
}

//ForwardIterator類型定義
//單純的傳遞調用(trivial forwarding function)
template<class ForwardIterator, class Distance>
inline void __advance(ForwardIterator& i, Distance n, forward_iterator_tag) {
    _advance(i,n,input_iterator_tag());
}


//BidirectionalIterator類型定義
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;    
}

//RandomAccessIterator類型定義
template<class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
                      random_access_iterator_tag) {
    i += n;
} 

這裏還需要一個對外開放的上層控制接口,調用上述的四個函數。 

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

任何一個迭代器,其類型永遠應該在該迭代器所隸屬之各種類型中,最強化的那個。int*屬於四個迭代器類型,但是它應該歸屬於random_access_iterator_tag。

注意到advance()的template參數名稱好像不理想。

template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)

advance既然可以接受各種類型的迭代器,就不應該將其型別參數命名爲InputIterator。這其實是STL算法的命名規則,以算法所能夠接受最低階迭代器類型來爲其迭代器型別參數命名

由於之前的五種迭代器分類標籤以繼承形式進行了定義,所以在上述函數advance中,可以將forward iterator的函數去掉。當傳入的是一個forward_iterator_tag的時候,由於繼承關係,自動匹配到其父類,input_iterator_tag中。從而調用input iterator的函數。

std::iterator的保證

爲了符合規範,任何迭代器都應該提供五個內嵌型別,以利於traits萃取,否則就是自別與整個STL架構,可能無法與其它STL組件順

利搭配。爲了方便設計新的iterator,STL提供了一個iterator class。使得每個新設計的迭代器都繼承於它,就可以保證五個迭代器型

別不被遺漏。

template<class Category,
         class T,
         class Distance=ptrdiff_t,
         class Pointer=T*,
         class Reference =T&>
struct iterator{
    typedef Category iterator_category;
    typedef T value_type;
    typedef Distance difference_type;
    typedef Pointer pointer;
    typedef Reference reference;
}

iterator class 不包含任何成員,純粹只是型別定義,所以繼承它不會導致任何額外負擔。後三個參數都有默認值,所以新的迭代器只

需要提供前兩個參數就可以。

SGI STL的私房菜:__type_traits

STL只對迭代器加以規範,制定出了iterator_traits這樣的東西。

SGI把這種技法進一步擴大到迭代器以外的時間,於是有了所謂的__type_traits。

雙底線前綴標定這是STI STL內部所用的東西,不在STL標準規範之內

iterator_traits負責萃取迭代器的特性,__type_traits負責萃取型別的特性。這裏我們關注的型別特性主要是指:

  • 是否具備non-trivial default ctor?
  • 是否具備non-trivial copy ctor?
  • 是否具備non-trivial assignment operator?
  • 是否具備non-trivial dtor?
  • 是否爲POD(plain old data)型別?

其中non-trivial意指非默認的相應函數,編譯器會爲每個類構造以上四種默認的函數,如果有非默認的函數,就會用編譯器執行該函數,如果使用默認的函數,我們可以使用memcpy(),memmove(),malloc()等函數來加快速度,提高效率.

且__iterator_traits允許針對不同的型別屬性在編譯期間決定執行哪個重載函數而不是在運行時才處理, 這大大提升了運行效率. 這就需要STL提前做好選擇的準備. 是否爲POD, non-trivial型別用__true_type和__false_type 來區分.

對於兩個參數我們不能將參數設爲bool值, 因爲需要在編譯期就決定該使用哪個函數, 所以需要利用函數模板的參數推導機制, 將__true_type__false_type表現爲一個空類, 就不會帶來額外的負擔, 又能表示真假, 還能在編譯時類型推導就確定執行相應的函數.

struct __true_type {};
struct __false_type {};

然後是SGI對於__type_traits的泛化版本,首先把所有內嵌型別都做最保守的值,然後針對每一個標量型別設計適當的__type_traits的

特化版本。

template <class type>
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;
};

以char*爲例的特化版本

struct __type_traits<char*> {
   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;
};

        上述五個typedefs通過以下途徑獲取實值:

  • 一般具現體,內含對所有型別都必定有效的保守值。比如都設置爲__false_type。
  • 經過聲明的特化版本,例如:<type_traits.h>內對所有C++標量型別提供了對應的特化聲明。
  • 某些編譯器會自動爲所有型別提供適當的特話版本。

SGI對traits進行擴展,使得所有類型都滿足traits編程規範, 這樣SGI STL算法可以通過__type_traits獲取類型信息在編譯期間就能決定

出使用哪一個重載函數, 解決了template是在運行時決定重載選擇的問題. 並且通過true和false來確定POD和travial destructor, 讓程

序能選擇更加符合其類型的處理函數, 大大提高了對基本類型的快速處理能力並保證了效率最高.

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