STL源码学习(一)迭代器概念与traits编程技法
STL的中心思想在于:將數據容器和算法分開,彼此獨立設計,最后再以一貼膠著劑將它們撮合在一起。這種膠著劑便是迭代器,迭代器設計的目的在于可以方便的獲取(遍歷)容器中的數據同時又不暴露容器的內部實現。
比如說,當使用一個鏈表迭代器時,使用者根本不會知道數據在鏈表中是如何存儲的。這帶來的好處是,所有的STL算法只需要接收迭代器最為參數就夠了,根本不用管是哪個容器的迭代器,只要能夠獲取數據,通過迭代器改變數據就可以了。
不過,迭代器的實現并不是那么簡單,因為在通常使用迭代器時,可能還需要知道迭代器指向的數據類型,比如某些算法中需要定義一個同類型的變量。這就產生了矛盾,因為既不想暴露底層數據,又必須知道數據的類型,后面會看到,通過traits技法,可以很容易獲取迭代器所指類型
類型萃取
traits技法
作為容器專屬的迭代器,容器中保存的數據類型迭代器是知道的(因為需要作為模板參數),所以如果在迭代器中定義內嵌類型,就可以獲取迭代器所指數據的類型,比如定義MyIter迭代器
template <class T> struct MyIter {/* 定義T的別名 */typedef T value_type;T* ptr;MyIter(T* p = 0) : ptr(p) {}T& operator*() const { return *ptr; }//... };templace <class I> /* 使用I的內嵌了類型獲取迭代器所指數據類型 */ typename I::value_type func(I ite) {return *ite; }//... MyIter<int> ite(new int(8)); cout << func(ite); //輸出8當使用作用域符號表達一個類型時,需要在前面添加typename顯式指出這是一個型別。因為I是一個模板參數,在具現化之前,編譯器對I一無所知,根本不知道I::value_type是什么,所以需要顯式告訴編譯器,這是一個型別
Traits就是對上述內嵌類型獲取迭代器所指數據類型方法的封裝,形象地稱為”萃取”迭代器特性,而value_type正是迭代器特性之一
/* 用于萃取迭代器類型,迭代器指向元素的類型等* 使用時可以直接使用* iterator_traits<I>::value_type獲取迭代器指向元素類型 */ template <class Iterator> struct iterator_traits {typedef typename Iterator::value_type value_type; };通過這個萃取器,如果Iterator聲明有value_type內嵌類型,那么就可以使用如下方法獲取迭代器Iterator所指類型
templace <class I> /* 函數返回型別,通過iterator_traits從I中萃取出它保存的數據類型 */ typename iterator_traits<I>::value_type func(I iter) {return *iter; }指針特化版本
多了一層間接性帶來的好處是可以為iterator_traits定義特化版本,由于原生指針也可以算作迭代器的一種,所以像iterator_traits\
/* 針對原生指針的偏特化版本,因為指針沒有內嵌類型 */ template <class T> struct iterator_traits<T*> {typedef T value_type; };/* 針對常量指針的偏特化版本 */ template <class T> struct iterator_traits<const T*> {typedef T value_type; };有了上面的特化版本,當使用iterator_traits\
內嵌類型
定義
迭代器部分只有iterator_traits萃取功能不容易理解,剩下的部分是關于迭代器型別的,包括
- value type,迭代器指向的數據值類型
- different type,迭代器差類型
- reference type,迭代器指向的數據引用類型
- pointer type,迭代器指向的數據指針類型
- iterator_category,迭代器類型
這幾種類型在萃取器iterator_traits中也都有相應的定義,用于萃取出相應型別,同上述的valut_type一樣
/* 用于萃取迭代器類型,迭代器指向元素的類型等 */ /* 使用時可以直接使用* iterator_traits<I>::iteartor_category獲取迭代器I的類型* iterator_traits<I>::value_type獲取迭代器指向元素類型* 依次類推 */ 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; };value type實際上就是迭代器所指對象的型別,也就是模板參數T。假設使用MyIter it迭代器,那么value type就是int
difference type是指迭代器之間的距離類型,如兩個迭代器作差,保存迭代器之間的距離等,其類型就是difference type,這個類型通常是ptrdiff_t類型
reference type是指迭代器所指對象的引用類型,通常是T&
pointer type是指迭代器所指對象的指針類型,通常是T*
迭代器類型
在STL中,根據迭代器所支持的算術運算,實際上存在多種類型的迭代器,分別是
- input iterator只讀迭代器,不可以改變迭代器所指對象。只支持++操作
- ouput iterator只寫迭代器,允許改變迭代器所指對象。只支持++操作
- forward iterator前向迭代器,可讀寫。只支持++操作
- bidirectional iterator雙向迭代器,可讀寫。支持++和–操作
- random access iterator隨機迭代器,可讀寫。支持所有指針算術操作,包括+,-,+=,-=,++,–等
可以看到,不同類型的迭代器可以提供不同的功能,但這同時也帶來了一些問題,以advance函數為例,該函數將給定的迭代器移動n步,然而,不同類型的迭代器支持不同的算術操作,如果都使用++,那么對隨機迭代器而言就過于耗時(因為本可以直接使用+=)。這就是iterator_category迭代器類型的作用,結合模板的參數推導區分不同的迭代器類型,同時采用最有效的方法進行算術操作
模板函數參數推導功能保證根據參數類型選擇最合適的重載函數,所以在STL中,提供了五個類定義分別對應五個迭代器類型
/* 定義迭代器類型,每個迭代器中都會將自己迭代器類型typedef成iterator_categories* 由于C++不支持直接獲取類型操作,所以需要利用模板參數推導實現不同類型的不同操作 */ 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 {};這五個類定義的繼承關系和迭代器之間的繼承關系相同,另外這五個類是空類,因為它的作用僅僅是作為模板參數用于函數重載
在迭代器的定義中會根據自身所支持的功能最強的迭代器類型定義iterator_category,如
typedef random_access_iterator_tag iterator_category;這樣,當再次使用advance函數時,傳入自己的迭代器類型對應的類,同時根據模板參數推導就可以調用不同的函數
/* 將迭代器i移動n步,n可正可負 */ template <class InputIterator, class Distance> inline void advance(InputIterator& i, Distance n) {/* 不同迭代器支持的操作不同* 只讀,只寫,正向迭代器只支持++操作* 雙向迭代器支持++,--操作* 隨機迭代器支持+=,-=,++,--操作* 為了根據不同迭代器選擇不同的__advance函數,采用模板參數推導的方法實現重載 *//* iterator_category()也是一個模板重載,用于創建一個迭代器i的tag對象* 根據模板參數推導,可以實現__advance重載 */__advance(i, n, iterator_category(i)); }模板參數中迭代器類型是InputIterator類型的原因是STL將模板參數類型設置為最弱的類型(類似繼承體系中的基類),實際類型會根據i實際的類型判斷(類似于繼承體系中的多態)
iterator_category函數用于獲取迭代器對應的tag對象
/* 返回迭代器類型對象,注意返回的是一個類對象* 這是因為模板參數推導只針對類對象進行推導*/ template <class Iterator> inline typename iterator_traits<Iterator>::iterator_category iterator_category(const Iterator&) {typedef typename iterator_traits<Iterator>::iterator_category category;return category(); }至此,根據__advance(i, n, iterator_category(i))函數調用的第三個參數類型,模板參數推導可以實現調用不同的函數
/* 針對只讀迭代器的__advance重載 */ template <class InputIterator, class Distance> inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {while (n--) ++i; }/* 針對雙向迭代器的__advance重載 */ template <class BidirectionalIterator, class Distance> inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag) {if (n >= 0)while (n--) ++i;elsewhile (n++) --i; }/* 針對隨機迭代器的__advance重載 */ template <class RandomAccessIterator, class Distance> inline void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) {i += n; }STL中只提供了三個__advance函數重載,這是因為上述的繼承體系。由于output迭代器,forward迭代器的__advane操作和input迭代器的操作相同,所以完全可以向上調用input迭代器的對應函數(好比調用派生類的某個函數,但是派生類中不存在這個函數,就會向上調用基類的對應函數)
小結
迭代器部分的難點有兩個,一個是通過traits技法實現類型萃取,另一個是通過模板參數推導實現函數重載。不過真正看過源碼后會發現其實并不困難,當然,STL需要內部的迭代器都遵循它的規定,即定義上述的五個型別
總結
以上是生活随笔為你收集整理的STL源码学习(一)迭代器概念与traits编程技法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每天一道LeetCode-----计算二
- 下一篇: 每天一道LeetCode-----找到所