Fluent C++:Ranges:STL的高級(jí)用法

原文

正如在一篇專門的帖子中看到的,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ù)的分支。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乍钻,一起剝皮案震驚了整個(gè)濱河市肛循,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌银择,老刑警劉巖多糠,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異浩考,居然都是意外死亡夹孔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門析孽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搭伤,“玉大人,你說(shuō)我怎么就攤上這事袜瞬×” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵邓尤,是天一觀的道長(zhǎng)拍鲤。 經(jīng)常有香客問我,道長(zhǎng)裁赠,這世上最難降的妖魔是什么殿漠? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮佩捞,結(jié)果婚禮上绞幌,老公的妹妹穿的比我還像新娘。我一直安慰自己一忱,他們只是感情好莲蜘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帘营,像睡著了一般票渠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芬迄,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天问顷,我揣著相機(jī)與錄音,去河邊找鬼。 笑死杜窄,一個(gè)胖子當(dāng)著我的面吹牛肠骆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塞耕,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼蚀腿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了扫外?” 一聲冷哼從身側(cè)響起莉钙,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筛谚,沒想到半個(gè)月后磁玉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刻获,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年蜀涨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞎嬉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝎毡。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖氧枣,靈堂內(nèi)的尸體忽然破棺而出沐兵,到底是詐尸還是另有隱情,我是刑警寧澤便监,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布扎谎,位于F島的核電站,受9級(jí)特大地震影響烧董,放射性物質(zhì)發(fā)生泄漏毁靶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一逊移、第九天 我趴在偏房一處隱蔽的房頂上張望预吆。 院中可真熱鬧,春花似錦胳泉、人聲如沸拐叉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凤瘦。三九已至,卻和暖如春案铺,著一層夾襖步出監(jiān)牢的瞬間蔬芥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笔诵,地道東北人涤姊。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嗤放,于是被迫代替她去往敵國(guó)和親思喊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容