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