在開始講迭代器之前,先列舉幾個例子,由淺入深的來理解一下爲什麼要設計迭代器。
//對於int類的求和函數
int sum(int *a , int n)
{
int sum = 0 ;
for (int i = 0 ; i < n ; i++) {
sum += *a++;
}
return sum;
}
//對於listNode類的求和函數
struct ListNode {
int val;
ListNode * next;
};
int sum(ListNode * head) {
int sum = 0;
ListNode *p = head;
while (p != NULL) {
sum += p->val;
p=p->next;
}
return sum;
}
針對如上兩個函數,我們來談談這種設計的缺陷所在:
- 遍歷int數組和單鏈表listNode時,需要設計兩份不一樣的sum求和函數,對於STL這樣含有大量容器的代碼庫,針對每一種容器都設計sum的話,過於冗雜
- 在sum函數中暴露了太多設計細節,如ListNode的節點值類型int,和指向下一個節點的指針next
- 對於int數組來說,還必須知道數組的大小,以免越界訪問
- 算法的設計過多的依賴容器,容器的改動會造成大量算法函數的隨之改動
那麼,如何設計才能使得算法擺脫特定容器?如何讓算法和容器各自獨立設計,互不影響又能統一到一起?本篇博客就帶你一窺STL的迭代器設計。
迭代器概述
迭代器是一種抽象的設計概念,在設計模式中是這麼定義迭代器模式的,即提供一種方法,使之能夠巡訪某個聚合物所含的每一個元素,而無需暴露該聚合物的內部表述方式。
不論是泛型思維或STL的實際運用,迭代器都扮演着重要的角色,STL的中心思想就在於:將數據容器和算法分開,彼此獨立設計,最後再以一帖膠着劑將它們撮合在一起。
談到迭代器需要遍歷容器就想到指針,的確,迭代器就是一種類似指針的對象,而指針的各種行爲中最常見也最重要的就是內容提領(dereference)和成員訪問(member access),因此,迭代器最重要的編程工作就是對operator*和operator->進行重載工作。
Traits編程技法
在介紹STL迭代器之前,先來看看Traits編程技法,通過它你能夠更好的理解迭代器設計。
template參數推導機制
我們先回過頭去看看sum函數,在sum函數中,我們必須知道容器的元素類型,這關係到函數返回值。既然迭代器設計中不能暴露容器的實現細節,那麼我們在算法中是不可能知道容器元素的類型,因此,必須設計出一個機制,能夠提取出容器中元素的類型。看看如下示例代碼:
#include <stdio.h>
#include <iostream>
using namespace std;
//此函數中並不知道iter所指的元素型別,而是通過模板T來獲取的
template <class I, class T1 ,class T>
T sum_impl(I iter ,T1 n , T t) {
T sum = 0;//通過模板的特性,可以獲取I所指之物的型別,此處爲int
//這裏做func應該做的工作
for(int i = 0 ; i < n ;i++){
sum+=*iter++;
}
return sum;
}
template <class I , class T1>
inline T1 sum(I iter , T1 n) {//此處暴露了template參數推導機制的缺陷,在型別用於返回值時便束手無策
return sum_impl(iter , n ,*iter);
}
int main() {
int a[5] = {1,2,3,4,5};
int sum1 = sum(a , 5);
cout<<sum1<<endl;
}
上例中通過模板的參數推導機制推導出了iter所指之物的型別(int),於是可以定義T sum變量。
然而,迭代器所指之物的型別並非只是”迭代器所指對象的型別“,最常用的迭代器型別有五種,並非任何情況下任何一種都可利用上述的template參數推導機制來獲取,而且對於返回值類型,template參數推導機制也束手無策,因此,Traits技法應運而生,解決了這一難題!
內嵌型別機制
Traits技法採用內嵌型別來實現獲取迭代器型別這一功能需求,具體怎麼實現的呢?我們看下面的代碼:
#include <stdio.h>
#include <iostream>
using namespace std;
//定義一個簡單的iterator
template <class T>
struct MyIter{
typedef T value_type; // 內嵌型別聲明
T* ptr;
MyIter(T* p =0):ptr(p){}
T& operator*() const {return *ptr;}
};
template <class I>
typename I::value_type // func返回值型別
func(I iter){
return *iter;
}
int main(){
MyIter<int> iter(new int(8));
cout<<func(iter)<<endl;
}
注意,func()函數的返回值型別必須加上關鍵詞typename,因爲T是一個template參數,在它被編譯器具現化之前,編譯器對其一無所知,關鍵詞typename的用意在於告訴編譯器這是一個型別,如此才能通過編譯。
內嵌型別看起來不錯,可以很順利的提取出迭代器所指型別並克服了template參數推導機制不能用於函數返回值的缺陷。可是,並不是所有的迭代器都是class type,原聲指針就不是!如果不是class type,那麼就無法聲明內嵌型別。但是STL絕對必須接受原生指針作爲一個迭代器。因此,必須另行它法!
iterator_traits萃取機
針對原生指針這類特殊情況,我們很容易想到利用模板偏特化的機制來實現特殊聲明,在泛化設計中提供一個特化版本。偏特化的定義是:針對任何template參數更進一步的條件限制所設計出來的一個特化版本。這裏,針對上面的MyIter設計出一個適合原生指針的特化版本,如下:
template <class T>
struct MyIter <T*>{ //T*代表T爲原生指針,這便是T爲任意型別的一個更進一步的條件限制
typedef T value_type; // 內嵌型別聲明
T* ptr;
MyIter(T* p =0):ptr(p){}
T& operator*() const {return *ptr;}
};
有了上述的介紹,包括template參數推導,內嵌型別,模板偏特化等,下面STL的真正主角要登場了,STL專門設計了一個iterator_traits模板類來”萃取“迭代器的特性。其定義如下:
// 用於traits出迭代其所指對象的型別
template <class I>
struct iterator_traits
{
typedef typename I::value_type value_type;
};
這個類如何完成迭代器的型別萃取呢?我們繼續往下看:
template <class I>
typename iterator_traits<I>::value_type // 通過iterator_traits類萃取I的型別
func(I iter){
return *iter;
}
從表面上來看,這麼做只是多了一層間接性,但是帶來的好處是極大的!iterator_traits類可以擁有特化版本,如下:
//原生指針特化版本
template <class T>
struct iterator_traits <T*>
{
typedef T value_type;
}
//const指針特化版本
template <class T>
struct iterator_traits <const T*>
{
typedef T value_type;
}
於是,原生指針int*雖然不是一種class type,也可以通過traits取其value,到這裏traits的思想就基本明瞭了!下面就來看看STL只能中”萃取機“的源碼
// 用於traits出迭代其所指對象的型別
template <class Iterator>
struct iterator_traits
{
// 迭代器類型, STL提供五種迭代器
typedef typename Iterator::iterator_category iterator_category;
// 迭代器所指對象的型別
// 如果想與STL算法兼容, 那麼在類內需要提供value_type定義
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;
};
對於源碼中的五種迭代器型別在下一小節中會有詳細說明。
迭代器設計
迭代器型別
value_type
所謂value_type,是指迭代器所指對象的型別,在之前的示例中已經介紹得很清楚了,這裏就不再贅述。
difference_type
difference_type用來表示兩個迭代器之間的距離,因此它也可以用來表示一個容器的最大容量。下面以STL裏面的計數功能函數count()爲例,來介紹一下difference_type的用法。
template <class I,class T>
typename iterator_traits<I>::difference_type //返回值類型
count(I first , I end , const T& value){
typename iterator_traits<I>::difference_type n = 0;
for( ; first!=end ; ++first){
if(*first == value) ++n;
}
return n;
}
針對原生指針和原生的const指針,iterator_traits的difference_type爲ptrdiff_t(long int),特化版本依然可以採用iterator_traits::difference_type來獲取型別。
reference_type
從“迭代器所指之物的內容是否可以改變”的角度可以將迭代器分爲兩種:
- const迭代器:不允許改變“所指對象之內容”,例如const int* p
- mutable迭代器:允許改變“所指對象之內容”,例如int* p
當我們要對一個mutable迭代器進行提領(reference)操作時,獲得的不應該是一個右值(右值不允許賦值操作),而應該是一個左值,左值才允許賦值操作。
故:
+ 當p是一個mutable迭代器時,如果其value type是T,那麼*p的型別應該爲T&;
+ 當p是一個const迭代器時,*p的型別爲const T&
pointer type
迭代器可以傳回一個指針,指向迭代器所指之物。再迭代器源碼中,可以找到如下關於指針和引用的實現:
Reference operator*() const { return *value; }
pointer operator->() const { return &(operator*()); }
在iterator_traits結構體中需要加入其型別,如下:
template <class Iterator>
struct iterator_traits
{
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
}
//針對原生指針的特化版本
template <class T>
struct iterator_traits<T*>
{
typedef T* pointer;
typedef T& reference;
};
//針對原生const指針的特化版本
template <class T>
struct iterator_traits<const T*>
{
typedef const T* pointer;
typedef const T& reference;
};
iterator_category
這一型別代表迭代器的類別,一般分爲五類:
- Input Iterator:只讀(read only)
- Output Iterator:只寫(write only)
- Forward Iterator:允許“寫入型”算法在此迭代器所形成的區間上進行讀寫操作
- Bidirectional Iterator:可雙向移動的迭代器
- Random Access Iterator:前四種迭代器都只供應一部分指針的算數能力(前三種支持operator++,第四種支持operator–),第五種則涵蓋所有指針的算數能力,包括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 {};
迭代器的保證
爲了符合規範,任何迭代器都應該提供五個內嵌型別,以利於Traits萃取,否則就自別與整個STL架構,可能無法與其他STL組件順利搭配。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;
};
迭代器源碼(完整版)
/**
* 用於標記迭代器類型
*/
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 {};
/**
* 用於traits出迭代其所指對象的型別
*/
template <class Iterator>
struct iterator_traits
{
// 迭代器類型, STL提供五種迭代器
typedef typename Iterator::iterator_category iterator_category;
// 迭代器所指對象的型別
// 如果想與STL算法兼容, 那麼在類內需要提供value_type定義
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();
}
/**
* 返回表示迭代器距離的類型
*/
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&)
{
return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}
/**
* 返回迭代器所指對象的類型
*/
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&)
{
return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}
迭代器相關函數設計
distance函數
distance函數用來計算兩個迭代器之前的距離。
- 針對Input Iterator,Output Iterator,Forward Iterator,Bidirectional Iterator來說,必須逐一累加計算距離
- 針對random_access_iterator來說,支持兩個迭代器相減,所以直接相減就能得到距離
其具體實現如下:
/**
* 針對Input Iterator,Output Iterator,Forward Iterator,Bidirectional Iterator
* 這四種函數,需要逐一累加來計算距離
*/
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag)
{
iterator_traits<InputIterator>::difference_type n = 0;
while (first != last) {
++first; ++n;
}
return n;
}
/**
* 針對random_access_iterator,可直接相減來計算差距
*/
template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last,
random_access_iterator_tag)
{
return last - first;
}
// 入口函數,先判斷迭代器類型iterator_category,然後調用特定函數
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last)
{
typedef typename iterator_traits<InputIterator>::iterator_category category;
return __distance(first, last, category());
}
Advance函數
Advance函數有兩個參數,迭代器p和n,函數內部將p前進n次。針對不同類型的迭代器有如下實現:
- 針對input_iterator和forward_iterator,單向,逐一前進
- 針對bidirectional_iterator,雙向,可以前進和後退,n>0和n<0
- 針對random_access_iterator,支持p+n,可直接計算
其代碼實現如下:
/**
* 針對input_iterator和forward_iterator版本
*/
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
while (n--) ++i;
}
/**
* 針對bidirectional_iterator版本
*/
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;
}
/**
* 針對random_access_iterator版本
*/
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_category(i));
}
附加:type_traits設計
iterator_traits負責萃取迭代器的特性;type_traits負責萃取型別的特性。
在帶你深入理解STL之空間配置器(思維導圖+源碼)一篇博客中,講到需要根據對象構造函數和析構函數的trivial和non-trivial特性來採用最有效的措施,例如:如果構造函數是trivial的,那麼可以直接採用如malloc()和memcpy()等函數,來提高效率。
例如:如果拷貝一個未知類型的數組,如果其具有trivial拷貝構造函數,那麼可以直接利用memcpy()來拷貝,反之則要調用該類型的拷貝構造函數。
type_traits的源代碼實現如下:
/**
* 用來標識真/假對象,利用type_traits響應結果來進行參數推導,
* 而編譯器只有面對class object形式的參數纔會做參數推導,
* 這兩個空白class不會帶來額外負擔
*/
struct __true_type{};
struct __false_type{};
/**
* type_traits結構體設計
*/
template <class type>
struct __type_traits
{
// 不要移除這個成員
// 它通知能自動特化__type_traits的編譯器, 現在這個__type_traits template是特化的
// 這是爲了確保萬一編譯器使用了__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, signed char, unsigned char,
// short, unsigned short
// int, unsigned int
// long, unsigned long
// float, double, long double
/**
* 以下針對C++內置的基本數據類型提供特化版本,
* 使其具有trivial default constructor,
* copy constructor, assignment operator, destructor並標記其爲POD類型
*/
__STL_TEMPLATE_NULL 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;
};
//針對char的特化版本
__STL_TEMPLATE_NULL struct __type_traits<signed 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;
};
//針對unsigned char的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned 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;
};
//針對short的特化版本
__STL_TEMPLATE_NULL struct __type_traits<short>
{
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;
};
//針對unsigned short的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned short>
{
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;
};
//針對int的特化版本
__STL_TEMPLATE_NULL struct __type_traits<int>
{
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;
};
//針對unsigned int的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned int>
{
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;
};
//針對long的特化版本
__STL_TEMPLATE_NULL struct __type_traits<long>
{
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;
};
//針對unsigned long的特化版本
__STL_TEMPLATE_NULL struct __type_traits<unsigned long>
{
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;
};
//針對float的特化版本
__STL_TEMPLATE_NULL struct __type_traits<float>
{
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;
};
//針對double的特化版本
__STL_TEMPLATE_NULL struct __type_traits<double>
{
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;
};
//針對long double的特化版本
__STL_TEMPLATE_NULL struct __type_traits<long double>
{
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;
};
// 針對指針提供特化
template <class T>
struct __type_traits<T*>
{
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;
};
// 針對char *, signed char *, unsigned 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;
};
struct __type_traits<signed 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;
};
struct __type_traits<unsigned 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;
};
後記
本篇博客比較枯燥,STL的迭代器中有很多優秀的值得學習的設計方式,如萃取機制,用類繼承來定義迭代器類型等,通篇代碼比較多,圖片講解較少,只能說迭代器的設計中基本上都是較爲繁瑣的型別聲明和定義,認真看下去的話會收穫很多!若有疑惑可以在博文下方留言,我看到會及時幫大家解答!
參考:
+ C++ STL源碼剖析
+ STL源碼剖析——迭代器