正如在一篇專門的帖子中看到的,C++標(biāo)準(zhǔn)模板庫(kù)(STL)是一個(gè)神奇的工具膏燕,它使代碼更加正確和富有表現(xiàn)力钥屈。主要分為兩部分:
- 容器,例如std::vector或者std::map等坝辫。
- 算法篷就,大多數(shù)用來(lái)操作容器的眾多通用函數(shù)。它們大多在<algorithm>頭文件中找到阀溶。
對(duì)于很多使用for循環(huán)遍歷容器的手工操作都可以用調(diào)用STL的算法來(lái)代替腻脏。這樣做的效果是使代碼更清晰鸦泳,因?yàn)榇a的閱讀者不必在腦海中分析復(fù)雜的for循環(huán)银锻,只要將循環(huán)語(yǔ)句用std::copy永品,std::partition或者std::rotate等顯式名稱代替,就可以立即理解所發(fā)生的事情击纬。
然而鼎姐,STL有幾個(gè)方面可以改進(jìn)。在本帖中更振,我們將重點(diǎn)討論其中的兩個(gè)問題:
-
所有算法操作指向它們所操作的容器的迭代器炕桨。雖然在一些特殊的情況下,如在容器的某一精確點(diǎn)停止肯腕,這樣做很方便献宫,但通常的情況是遍歷整個(gè)容器,從它的.begin ()到它的.end ()实撒。使用STL的代碼部分最終會(huì)充滿迭代器:
std::copy(v1.begin(), v1.end(), std::back_inserter(v2)); std::set_difference(v2.begin(), v2.end(), v3.begin(), v3.end(), std::back_inserter(v4)); std::transform(v3.begin(), v3.end(), std::back_inserter(v4));
(注:上面使用的std::back_inserter是一個(gè)輸出迭代器姊途,它每次被分配給容器時(shí),都使用容器的push_back方法知态。這減輕了程序員對(duì)輸出大小的估量)
-
算法不好組合使用捷兰。我發(fā)現(xiàn)使用STL的C++開發(fā)人員經(jīng)常遇到的一個(gè)需求是,只在容器中滿足謂詞的元素上應(yīng)用函數(shù)负敏。對(duì)容器輸入的所有元素應(yīng)用函數(shù)f并將結(jié)果放入vector中輸出是通過std::transform:
std::transform(input.begin(), input.end(), std::back_inserter(output), f);
根據(jù)謂詞p過濾元素是使用std::copy_if:
std::copy_if(input.begin(), input.end(), std::back_inserter(output), p);
但是贡茅,要合并這兩個(gè)調(diào)用并不容易,也沒有“transform_if”算法這樣的東西其做。
ranges以一種非常優(yōu)雅的方式來(lái)解決STL的這兩個(gè)問題顶考。ranges最初是在Boost中引入的,現(xiàn)在正在走向標(biāo)準(zhǔn)化妖泄。我相信它們將對(duì)我們處理代碼中容器的方式產(chǎn)生重大影響村怪。
Range的概念
Range的概念是重中之重。從本質(zhì)上講浮庐,Range是可以遍歷的東西甚负。更確切地說(shuō),Range是一個(gè)包含begin()和end()方法的東西审残,它返回對(duì)象(迭代器)來(lái)允許你遍歷Range(即沿著Range的元素移動(dòng)梭域,并以解引用的方式訪問這些元素)。
用偽代碼表示的Range符合以下接口:
Range
{
begin()
end()
}
請(qǐng)注意搅轿,這意味著所有STL容器本身都是Range病涨。
在定義Range概念之前,使用STL的代碼已經(jīng)以某種方式使用了Range璧坟,但是使用起來(lái)很笨拙既穆。正如本文開頭所看到的赎懦,它們直接由兩個(gè)迭代器操作,典型的一個(gè)begin幻工,一個(gè)end励两。但是對(duì)于Range,通衬衣看不到迭代器当悔。他們?cè)谶@里,但被Range的概念所抽象化踢代。
理解這一點(diǎn)很重要盲憎。迭代器是允許你對(duì)容器進(jìn)行迭代的技術(shù)性數(shù)據(jù)結(jié)構(gòu),但它們通常對(duì)于函數(shù)代碼來(lái)說(shuō)太技術(shù)性了胳挎。在大多數(shù)情況下饼疙,你真正想表示的是一個(gè)范圍,它更符合代碼的抽象級(jí)別慕爬。就像現(xiàn)金流的范圍窑眯,屏幕上的行的范圍,或者從數(shù)據(jù)庫(kù)中出來(lái)的條目的范圍澡罚。
因此伸但,從range的角度進(jìn)行編碼是一個(gè)巨大的改進(jìn),因?yàn)閺倪@個(gè)意義上說(shuō)迭代器違反了尊重抽象層次的原則留搔,我認(rèn)為這是設(shè)計(jì)好代碼最重要的原則更胖。
在range庫(kù)中,STL算法被重新定義隔显,以直接將range作為參數(shù)却妨,而不是兩個(gè)迭代器,例如:
ranges::transform(input, std::back_inserter(output), f);
而不是:
std::transform(input.begin(), input.end(), std::back_inserter(output), f);
此類算法在實(shí)現(xiàn)中重用STL版本括眠,方法是將range的begin和end轉(zhuǎn)發(fā)給原始STL版本彪标。
智能迭代器
盡管用range做了抽象了,但是range遍歷還是用迭代器實(shí)現(xiàn)的掷豺。range的全部能力來(lái)自它與智能迭代器的組合捞烟。一般來(lái)說(shuō),容器的迭代器有兩個(gè)職責(zé):
- 沿著容器的元素移動(dòng)(++当船、--等)
- 訪問容器元素(*, ->)
例如题画,一個(gè)vector迭代器可以完成這個(gè)任務(wù)。但是源于boost的“智能”迭代器定制了其中一種或兩種行為德频。例如:
- transform_iterator由另一個(gè)迭代器it和一個(gè)函數(shù)(或函數(shù)對(duì)象)f構(gòu)造苍息,并自定義它訪問元素的方式:當(dāng)解引用時(shí),transform_iterator將f應(yīng)用到*it并返回結(jié)果。
- filter_iterator由另一個(gè)迭代器it和一個(gè)謂詞p構(gòu)造竞思。它定制它的移動(dòng)方式:當(dāng)向前移動(dòng)一個(gè)( ++ ) filter_iterator時(shí)表谊,它向前移動(dòng)它的基礎(chǔ)迭代器,直到它到達(dá)滿足謂詞或容器結(jié)尾的元素盖喷。
結(jié)合range和智能迭代器:range適配器
range的全部能力來(lái)自它們與智能迭代器的關(guān)聯(lián)爆办。這是用range適配器完成的。
range適配器是一種對(duì)象传蹈,它可以與range組合以產(chǎn)生新的range押逼。它們的一個(gè)子部分是視圖適配器:對(duì)于它們步藕,初始的被適配的range保持不變惦界,而生成的range不包含元素,因?yàn)樗c初始range相比更像是視圖咙冗,但具有定制的迭代行為沾歪。
為了說(shuō)明這一點(diǎn),我們以view::transformadaptor為例雾消。這個(gè)適配器用一個(gè)函數(shù)初始化灾搏,并且可以與一個(gè)range組合以產(chǎn)生一個(gè)視圖,這個(gè)視圖具有一個(gè)transform_iterator在這個(gè)范圍內(nèi)的迭代行為立润。range適配器可以與range結(jié)合使用狂窑,使用運(yùn)算符|,這為它們提供了優(yōu)雅的語(yǔ)法桑腮。
使用如下的數(shù)字容器:
std::vector numbers = { 1, 2, 3, 4, 5 };
range:
auto range = numbers | view::transform(multiplyBy2);
是具有transform_iterator的迭代行為的數(shù)字容器的視圖泉哈,其函數(shù)為multiplyBy2。所以當(dāng)你遍歷這個(gè)視圖時(shí)破讨,你得到的結(jié)果就是這些數(shù)字乘以2丛晦。例如:
ranges::accumulate(numbers | view::transform(multiplyBy2), 0);
返回12 + 22 + 32 + 42 + 5*2 = 30 (類似 std::accumulate).
還有許多其他range適配器。例如提陶,view::filter接受一個(gè)謂詞烫沙,并且可以與range組合以通過filter_iterator的行為在謂詞上構(gòu)建視圖:
ranges::accumulate(numbers | view::filter(isEven), 0);
返回 22 + 42 = 12。這為最初不能將算法組合在一起的問題提供了一個(gè)解決方案隙笆。
結(jié)論
Range提高了使用STL的抽象級(jí)別锌蓄,通過刪除多余的迭代器來(lái)清理代碼。Range適配器是一種非常強(qiáng)大和富有表達(dá)性的工具撑柔,它以模塊化的方式對(duì)容器的元素進(jìn)行操作瘸爽。
Range是STL的未來(lái)。要進(jìn)一步了解乏冀,你可以查看一下初始range庫(kù)蝶糯,或者看看EricNiebler的標(biāo)準(zhǔn)化提案。由于該提案依賴于C++17中未包含的概念辆沦,因此range尚未標(biāo)準(zhǔn)化昼捍。在此之前识虚,你可以深入研究Eric Niebler的range庫(kù)range-v3 ,它與C++語(yǔ)言的當(dāng)前版本兼容妒茬。它可在VisualStudio2015Update3中找到担锤,并帶有流行的range-v3庫(kù)的分支。