其實,迭代器不必依存于容器。而是,先有了迭代器,才會有容器。請謹記,迭代器可以獨立存在。begin和end就代表了一堆數據的概念。至于這一堆數據是如何存放的,這一切都無關緊要。基于此,有必要用class來表達一堆數據這么一個通用性極高的概念。其實,boost里面好像也有這么一個東西。就叫做DataRange吧。為何不叫Range,因為Range另有更重要用途,這么好的名字就是用來生成DataRange,代碼不會直接看到DataRange,都是通過Range來生成DataRange。
然后,隨便搞兩行代碼試試
vector<int> vvv = { 1, 2, 3 };
for (auto i : Range(vvv))
{
cout << i << endl;
}
其實,C++11概念上就支持一堆數據的操作,只要一個類型struct或者class里面有begin()和end()這一對活寶,并且這一對活寶的返回類型是迭代器,那么就可以盡情的享用foreach的甜糖。那么,何謂迭代器。就是支持三種操作的數據類型:!=(判斷相等,用來結束迭代操作),前++(用來到迭代到下一個元素),*(取值)。那么,這就是迭代器了,顯然,指針就是原生的迭代器。雖然,整形int也可以++,!=,但是不支持取值操作,所以int不是迭代器。下面就要把int變成迭代器。
template<typename Ty, typename Step>
struct ValueIncrementIterator
{
typedef ValueIncrementIterator ThisType;
typedef Ty ValueType;
typedef Step StepType;
ThisType(ValueType val, StepType step)
:mValue(val), mStep(step){}
bool operator != (const ThisType& other) const
{
return mValue < other.mValue;
}
ValueType operator* () const
{
return mValue;
}
ThisType& operator++ ()
{
mValue += mStep;
return *this;
}
ValueType mValue;
StepType mStep;
};
然后,再用一個函數FromTo(也不知叫什么名字更好),用來生成DataRange。請注意,我們的迭代器怎么實現,那都是細節。最后展示在用戶層代碼都是干干凈凈的function生成的DataRange,甚至連尖括號都不見了。也不用寫具體是什么類型的DataRange,只須用auto讓編譯器自動推導類型就好了。
// step = 1是偷懶做法,萬一Step的構造函數不能以1為參數就弱雞了。比如DateTime和TimeSpan
template<typename Ty, typename Step>
auto FromTo(Ty from, Ty to, Step step = 1) -> DataRange<ValueIncrementIterator<Ty, Step>>
{
typedef ValueIncrementIterator<Ty, Step> ValueType;
return DataRange<ValueType>(ValueType(from, step), ValueType(to, step));
}
于是,FromTo(1, 10, 2)就表示10以內的所有奇數,可以用for range的語法糖打印出來。
這里的FromTo是按照上升狀態產生一系列數據,同樣,也可以產生下降的一堆數據FromDownTo,如果愿意的話,同學們也可以用迭代器形式生成斐波那契數列。不知注意到了,請用抽象的角度理解++和*這兩個操作符。++就是為新的數據做準備進入到下一個狀態,根據情況,可以有不同方式,進入到下一個狀態,比如上面的ValueIncrementIterator根據步長遞增到新的數值,ValueDecrementIterator的++卻是在做減法,甚至還可以做Filter操作;*就是取到數據,我們可以在*的時候,才生成一個新的數據,這里從某種意義上來講,其實就是延遲求值;而!=判斷結束條件的方式又多種多樣。總之,憑著這三個抽象操作,花樣百出,基本上已經能夠覆蓋所有的需求了。
為了體現這種抽象的威力,讓我們給DataRange增加一個函數Concate,用于將兩堆數據串聯成一堆數據。首先,定義一個游走于兩堆數據的迭代器,當它走完第一堆數據,就進入第二堆數據。
//不知道有什么語法能推導迭代器的值類型,所以搞這個輔助函數。可能寫成type_trait形式更好,就算偷懶吧
template<typename Iter>
auto GetIteratorValueType(Iter* ptr) -> decltype(**ptr)
{
return **ptr;
}
template<typename Iter1, typename Iter2>
struct ConcateIterator
{
typedef ConcateIterator ThisType;
typedef Iter1 Iter1Type;
typedef Iter2 Iter2Type;
//typedef decltype(*mBegin1) ValueType;
typedef decltype(GetIteratorValueType((Iter1Type*)nullptr)) ValueType;
ThisType(Iter1Type begin1, Iter1Type end1, Iter2Type begin2)
:mBegin1(begin1), mEnd1(end1), mBegin2(begin2), mInBegin2(false){}
ThisType(Iter1Type end1, Iter2Type begin2) //這里有些蹊蹺,不過也沒什么
:mBegin1(end1), mEnd1(end1), mBegin2(begin2), mInBegin2(true){}
bool operator != (const ThisType& other) const
{
if (!mInBegin2 && other.mInBegin2)
return true;
if (!mInBegin2 && !other.mInBegin2 && mBegin1 != other.mBegin1)
return true;
if (mInBegin2 && other.mInBegin2 && mBegin2 != other.mBegin2)
return true;
return false;
}
ValueType operator* () const
{
return mInBegin2 ? (*mBegin2) : (*mBegin1);
}
ThisType& operator++ ()
{
if (mInBegin2)
{
++mBegin2;
}
else
{
if (mBegin1 != mEnd1)
++mBegin1;
if (!(mBegin1 != mEnd1))
mInBegin2 = true;
}
return *this;
}
Iter1Type mBegin1;
Iter2Type mBegin2;
Iter1Type mEnd1;
bool mInBegin2;
};
有了
ConcateIterator,DataRange的Concate函數就很好辦了。
template<typename OtherRange>
auto Concate(const OtherRange& otherRange)
->DataRange<ConcateIterator<IteratorType, decltype(otherRange.begin())>>
{
typedef ConcateIterator < IteratorType, decltype(otherRange.begin())> ResultIter;
return DataRange<ResultIter>(
ResultIter(mBegin, mEnd, otherRange.begin()), ResultIter(mEnd, otherRange.end()));
}
然后,試試
list<int> numList = { 10, 11, 12 };
for (auto i : Range(vvv).Concate(FromTo(4, 10, 2)).Concate(numList)) //后面隨便接容器
{
cout << i << endl;
}
這樣,就把兩堆數據串聯在一塊了,是不是很酷呢?用C++11寫代碼,很有行云流水的快感,又有函數式編程的風格。下期節目繼續發揮,給DataRange加入Filter,Map,Replace等操作,都是將一個DataRange變換成另一個DataRange的操作,顯然,這是一種組合子的設計方式,也是吸收了haskell和linq的設計思路。某種意義上講,就是給迭代器設計一套dsl,通過.操作符自由組合其成員函數,達到用起來很爽的效果,目標就是僅僅通過幾個正交成員函數的隨意組合,可以在大多數情況下代替stl算法的鬼麻煩的寫法。這種dsl的最大好處類似于linq,先處理的步驟寫在最前面,避開了函數調用的層次毛病,最外層的函數反而寫在頂層。其實迭代器這個話題要展開來說的話,很有不少內容,比如用stackless協程來偽裝成迭代器,Foldl,Foldl1,Scan等。當然,真要用得爽,還要配合boost中lambda的語法,好比什么_1+30,_1%2,當然,那個也可以自己寫,因為C++現在已經支持lambda了,所以,自己寫boost lambda的時候,可以剪裁,取其精華,去其糟粕。如果,再弄一個支持arena內存批量釋放又或者是Stack風格的allocator(線程相關),那么就更不會有任何心智負擔了,內存的分配和釋放飛快,這樣的動多態的allocator寫起來也很有意思,它可以根據不同情況表現不同行為,比如說多線程下,就會用到線程同步,單線程就無須同步,每個線程單獨擁有一個allocator,根據用戶需要,還能用棧式內存分配,也就是分配內存時只是修改指針而已,釋放時就什么都不做了,最后通過析構函數,將此allocator的內存一次性釋放。當擁有一個表現如此多樣的allocator,stl用起來真是爽。