前面說了不少關於traits的光榮事蹟,可是卻一直沒有介紹traits到底是個什麼東西,到底是用來幹什麼的?traits在英文解釋中就是特性,下面將會引入traits技術的做用,一步一步地揭開其神祕的面紗。算法
下面是一個以迭代器爲模板形參的函數模板:編程
template<typename Iterator> void func(Iterator iter) { //函數體 }
假如如今算法中須要聲明一個變量,而變量的類型是迭代器所指對象的類型,應該怎麼處理呢?dom
template<typename Iterator> void func(Iterator iter) { *Iterator var;//這樣定義變量能夠嗎? }
上面的代碼是不能夠經過編譯的,雖然C++支持sizeof(),可是並不支持typeof(),就算是用到RTTI性質中的typeid(),獲取到的也僅僅是類型的名字,所以不能直接用來聲明變量。此時能夠利用函數模板的參數類型推導機制解決問題,例如:函數
template<typename Iterator, typename T> void func_impl(Iterator iter, T t) { T temp;//這裏就解決了問題 //這裏作本來func()的工做 } template<typename Iterator> void func(Iterator iter) { func_impl(iter, *iter);//func的工做所有都移到func_impl裏面了 } int main(int argc, const char *argv[]) { int i; func(&i); }
函數func做爲對外接口,實際的操做卻由函數func_impl執行,經過函數func_impl的參數類型推導,獲取到Iterator指向對象的類型T,從而解決了問題。學習
如今經過函數模板的參數類型推導解決了函數體內聲明變量的問題,但問題又來了,若是須要返回類型是迭代器所指對象的類型又能夠怎樣作呢?spa
template<typename Iterator> (*Iterator) func(Iterator iter) { //這樣定義返回類型能夠嗎? }
在這種狀況下,模板的參數類型推導機制也無能爲力了,由於它只能推導參數,並不能推導函數的返回類型。STL解決這種問題的辦法就是內嵌類型聲明,即在迭代器內部添加一種「特性」,經過這種「特性」,算法能夠很容易地獲知迭代器所指對象的類型,請看下面的代碼:.net
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,緣由在本人以前寫的「C++模板學習」中也解釋過,由於T是一個template參數,編譯器在編譯實例化func以前,對T一無所知,就是說,編譯器並不知道Iterator<T>::value_type是一個類型,或者是一個靜態成員函數,仍是一個靜態數據成員,關鍵詞typename的做用在於告訴編譯器這是一個類型,這樣才能順利經過編譯。設計
以前在介紹迭代器的分類之時說過,原生指針也是一種迭代器,此時問題就來了,原生指針並非一種類類型,它是沒法定義內嵌類型的。所以,上面的內嵌類型實現還不能徹底解決問題,那可不能夠針對原生指針作特殊化的處理呢?答案是確定的,利用模板偏特化就能夠作到了。指針
《泛型思惟》一書對模板偏特化的定義是:code
針對template參數更進一步的條件限制所設計出來的一個特化版本。
//這個泛型版本容許T爲任何類型 template<typename T> class C { //... };
咱們很容易接受上面的類模板有一個形式以下的偏特化版本:
template<typename T> class C<T*> { //... };
這個特化版本僅適用於T爲原生指針的狀況,」T爲原生指針」就是「T爲任何類型」的一個更進一步的條件限制。那如何利用模板偏特化解決原生指針不能內嵌類型的問題呢?下面介紹的iterator_traits就是關鍵了。
STL裏面使用iterator_traits這個結構來專門「萃取」迭代器的特性,前面代碼中提到的value_type就是迭代器的特性之一:
template<typename Iterator> struct iterator_traits { typedef typename Iterator::value_type value_type; };
若是Iterator有定義value_type,那麼經過iterator_traits做用以後,獲得的value_type就是Iterator::value_type,比較以前寫的版本和經iterator_traits做用後的版本:
template<typename Iterator> typename Iterator::value_type //這行是返回類型 func(Iterator iter) { return *iter; } //經過iterator_traits做用後的版本 template<typename Iterator> typename iterator_traits<Iterator>::value_type //這行是返回類型 func(Iterator iter) { return *iter; }
從長度上看,好像須要敲的代碼更多了,爲何要這麼麻煩加上一層間接層呢?因爲原生指針也是一種迭代器,並且不是一種類類型,所以原生指針並不能定義內嵌類型。這裏經過實現iterator_traits的一個偏特化版本就能夠解決這個問題了,具體的實現以下:
//iterator_traits的偏特化版本,針對迭代器是個原生指針的狀況 template<typename T> struct iterator_traits<T*> { typedef T value_type; };
你們在進行函數重載的時候,應該都曾遇到過如下的狀況:
//函數版本一 void func(int *ptr) { //... } //函數版本二 void func(const int *ptr) { //... }
以上兩個函數雖然函數、形參個數和位置都同樣,但它們不是同一個函數,而是函數重載的一種狀況,也就是說函數形參的const和非const版本是不同的,在函數版本一里面,能夠修改指針ptr指向的數據,可是在函數版本二里面卻不能夠,由於傳入的指針ptr是一個const指針。由此能夠聯想到,當將一個const指針做爲模板形參傳給前面聲明的偏特化版本的iterator_traits會有發生什麼狀況呢?
iterator_traits<const int*>::value_type //得到的value_type是const int,並非int
當咱們想用iterator_traits萃取出value_type並聲明一個臨時變量時,卻發現聲明的變量是const類型,並不能進行賦值,這違背了咱們的用意。咱們須要一種方法區別const和非const才能避免這種誤會的發生,答案很簡單,只要另外再設計一個iterator_traits偏特化版本就能夠了:
template<typename T> struct iterator_traits<const T*> { typedef T value_type; }
如今,不管是自定義的迭代器,仍是原生指針int*或者是const int*,均可以經過iterator_traits獲取到正確的value_type。
STL根據經驗,定義了迭代器最經常使用到的五種類型:value_type、difference_type、pointer、reference、iterator_category,任何開發者若是想將本身開發的容器與STL結合在一塊兒,就必定要爲本身開發的容器的迭代器定義這五種類型,這樣均可以經過統一接口iterator_traits萃取出相應的類型,下面列出STL中iterator_traits的完整定義:
tempalte<typename I> struct iterator_traits { typedef typename I::iterator_category iterator_category; typedef typename I::value_type value_type; typedef typeanme I:difference_type difference_type; typedef typename I::pointer pointer; typedef typename I::reference reference; };
下面會分別介紹一下這五種類型:
(1) 迭代器類型之一:value_type
value_type就是指迭代器所指對象的類型,例如,原生指針也是一種迭代器,對於原生指針int*,int即爲指針所指對象的類型,也就是所謂的value_type。
(2) 迭代器類型之二:difference_type
difference_type用來表示兩個迭代器之間的距離,例如:
int array[5] = {1, 2, 3, 4, 5}; int *ptr1 = array + 1;//指向2 int *ptr2 = array + 3;//指向4 ptrdiff_t distance = ptr2 - ptr1;//結果即爲difference_type
上面代碼中,指針ptr2與ptr1相減的結果的類型就是difference_type,對於原生指針,STL以C++內建的ptrdiff_t做爲原生指針的difference_type。
(3) 迭代器類型之三:reference_type
reference_type是指迭代器所指對象的類型的引用,reference_type通常用在迭代器的*運算符重載上,若是value_type是T,那麼對應的reference_type就是T&;若是value_type是const T,那麼對應的reference_type就是const T&。
(4) 迭代器類型之四:pointer
pointer就是指迭代器所指的對象,也就是相應的指針,對於指針來講,最經常使用的功能就是operator*和operator->兩個運算符。所以,迭代器須要對這兩個運算符進行相應的重載工做:
T& operator*() const { return *ptr; } // T& is reference type T* operator->() const { return ptr; } // T* is pointer type
(5) 迭代器類型之五:iterator_category
iterator_category的做用是標識迭代器的移動特性和能夠對迭代器執行的操做,從iterator_category上,可將迭代器分爲Input Iterator、Output Iterator、Forward Iterator、Bidirectional Iterator、Random Access Iterator五類,具體爲何要這樣分類,簡單來講,就是爲了儘量地提升效率,這也是STL的宗旨之一。具體的狀況已經在本人的「《STL源碼剖析》學習之迭代器」中詳細介紹過,這裏就不在多說了。
爲了保證iterator_traits能夠正常工做,STL提供了一個iterator類,全部自定義的迭代器都必須繼承自它,這樣才能保證這些自定義的迭代器能夠順利地獄其它STL組件進行協做,iterator類具體定義以下:
template<typename Category, typename T, typename Distance = ptrdiff_t, typename Pointer = T*, typename Reference = T&> struct iterator { typedef Category iterator_category; typedef T value_type; typedef Distance difference_type; typedef Pointer pointer; typedef Reference reference; };
類iterator不包含任何成員變量,只有類型的定義,所以不會增長額外的負擔。因爲後面三個類型都有默認值,在繼承它的時候,只須要提供前兩個參數就能夠了,如: