C++ STL 源碼閱讀 (四): sort

qsort vs std::sort

朋友問我疆液,qsort和std::sort有什么區(qū)別,我沒有專門查過,但還是嘗試答了幾條:

  1. qsort是C標(biāo)準(zhǔn)庫函數(shù)相艇,位于<stdlib.h>;sort是STL中的函數(shù)模板纯陨,位于<algorithm>
  2. qsort的參數(shù)用指針表示范圍坛芽;sort的參數(shù)用迭代器表示范圍
  3. qsort肯定是快排,sort應(yīng)該是根據(jù)迭代器類型來判斷是否采用快排翼抠,如果是前向迭代器的話應(yīng)該就不是快排

第三條是我猜的咙轩,后來查過資料之后,發(fā)現(xiàn)我第三條確實(shí)答錯(cuò)了阴颖,事實(shí)上:

  • sort的迭代器參數(shù)只能是Mutable RandomAccessIterator活喊。
  • sort的排序算法不是快排,而是IntroSort和InsertionSort的結(jié)合量愧。(內(nèi)省排序 和 插入排序)

下面說一下std::sort的源碼钾菊,以及記錄一下閱讀中遇到的不懂的東西(不懂的實(shí)在有點(diǎn)多,可能有點(diǎn)啰嗦)侠畔。

std::sort

std::sort有兩個(gè)重載:

  template<typename _RAIter>
    void 
    sort(_RAIter, _RAIter);

  template<typename _RAIter, typename _Compare>
    void 
    sort(_RAIter, _RAIter, _Compare);

分別點(diǎn)進(jìn)去:

  template<typename _RandomAccessIterator>
    inline void
    sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
    {
      // concept requirements
      __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
        _RandomAccessIterator>)
      __glibcxx_function_requires(_LessThanComparableConcept<
        typename iterator_traits<_RandomAccessIterator>::value_type>)
      __glibcxx_requires_valid_range(__first, __last);

      std::__sort(__first, __last, __gnu_cxx::__ops::__iter_less_iter());
    }

  template<typename _RandomAccessIterator, typename _Compare>
    inline void
    sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
     _Compare __comp)
    {
      // concept requirements
      __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
        _RandomAccessIterator>)
      __glibcxx_function_requires(_BinaryPredicateConcept<_Compare,
        typename iterator_traits<_RandomAccessIterator>::value_type,
        typename iterator_traits<_RandomAccessIterator>::value_type>)
      __glibcxx_requires_valid_range(__first, __last);

      std::__sort(__first, __last, __gnu_cxx::__ops::__iter_comp_iter(__comp));
    }

殊途同歸结缚,調(diào)用了同一個(gè)函數(shù)std::__sort,只是第三個(gè)參數(shù)不同软棺。

接下來先不看std::__sort的源碼红竭,先搞懂調(diào)用該函數(shù)前的那一堆代碼是什么。

__glibcxx_function_requires

上面代碼里面,__glibcxx_function_requires是宏定義茵宪,涉及到c++ concept checking的概念最冰。

顯然concept checking不是C++11標(biāo)準(zhǔn)的一部分,它是Boost的一部分稀火,是Boost Concept Checking Library的內(nèi)容暖哨,然后GNU將其整合到了 GNU C++ library中。gcc里面凰狞,concept checking默認(rèn)是關(guān)閉的篇裁,可以通過 #define _GLIBCXX_CONCEPT_CHECKS 來打開它,但是沒有任何必要赡若,因?yàn)镃++11的特性會(huì)完成concept checking的功能达布。更具體的解釋見文末的引用[6][7]。

簡而言之逾冬,這個(gè)concept check的作用是“限制某類型的特定的操作是合法的”黍聂。比如說,__glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<_RandomAccessIterator>)就是要限制你傳入的迭代器類型得是Mutable RandomAccessIterator身腻,得支持++ -- [] * += -= 等運(yùn)算符操作产还。如果不滿足的話,編譯就不通過嘀趟。

上面的sort(__first, __last)里的兩條__glibcxx_function_requires就限制了:

  • 迭代器類型得是Mutable RandomAccessIterator
  • 迭代器所指類型得是支持<運(yùn)算符的

之前我只清楚iterator的五種類型脐区,即std::input_iterator_tag等代表的五種,然后我就有了以下疑問:

  1. 為什么用concept check來檢查迭代器類型去件?像std::advance那樣用__iterator_traits檢查迭代器類型不行嗎坡椒?
  2. 迭代器類型中有mutable iterator嗎?五種迭代器類型好像沒有提到坝攘铩?

針對(duì)問題查了cppreference.com [4]:

There are five (until C++17)six (since C++17) kinds of iterators: InputIterator, OutputIterator, ForwardIterator, BidirectionalIterator, RandomAccessIterator, and ContiguousIterator (since C++17).

All of the iterator categories (except OutputIterator and ContiguousIterator) can be organized into a hierarchy, where more powerful iterator categories (e.g. RandomAccessIterator) support the operations of less powerful categories (e.g. InputIterator). If an iterator falls into one of these categories and also satisfies the requirements of OutputIterator, then it is called a mutable iterator and supports both input and output. Non-mutable iterators are called constant iterators.

mutable iterator指的支持write和increment的迭代器汗唱。因此宫莱,iterator_catogory是random_access_iterator_tag的迭代器并不一定可寫,random_access_iterator_tag并不能保證迭代器是mutable還是constant哩罪,只有mutable iterator才是可寫的授霸。所以上面的兩個(gè)問題有答案了。

那么concept check是怎么實(shí)現(xiàn)的呢际插?接下來接著看源碼碘耳。

點(diǎn)進(jìn)去__glibcxx_function_requires:

#ifndef _GLIBCXX_CONCEPT_CHECKS
#define __glibcxx_function_requires(...)
#else // the checks are on
#define __glibcxx_function_requires(...)                                 \
            __gnu_cxx::__function_requires< __gnu_cxx::__VA_ARGS__ >();
#endif // enable/disable

因此,如果想要使用concept check框弛,就需要自己在include相關(guān)頭文件之前定義宏_GLIBCXX_CONCEPT_CHECKS辛辨,或者修改c++config.h中的宏定義。

接下來看 __gnu_cxx::__function_requires。

template <class _Concept>
inline void __function_requires()
{
  void (_Concept::*__x)() _IsUnused = &_Concept::__constraints;
}

_IsUnused是宏定義斗搞,展開后是__attribute__((unused))指攒,表示該函數(shù)或變量可能不使用,這個(gè)屬性可以避免編譯器產(chǎn)生警告信息僻焚。如果看不懂__function_requires里面的語法允悦,請(qǐng)參考引用[2][3]。

所以虑啤,最初std::sort里的第一個(gè)__glibcxx_function_requires展開之后就是:

__gnu_cxx::__function_requires< __gnu_cxx::_Mutable_RandomAccessIteratorConcept<
        _RandomAccessIterator>>();

然而這做了什么隙弛?

首先,這句話導(dǎo)致實(shí)例化并調(diào)用了__function_requires模板函數(shù)狞山。該函數(shù)里全闷,定義了一個(gè)函數(shù)指針,指向模板參數(shù)表示的類的__constraints成員函數(shù)铣墨,進(jìn)而導(dǎo)致實(shí)例化了__gnu_cxx::_Mutable_RandomAccessIteratorConcept<_RandomAccessIterator>::__constraints室埋。在__constraints函數(shù)中會(huì)對(duì)迭代器進(jìn)行某些運(yùn)算符操作,編譯時(shí)編譯器會(huì)檢查這些操作的有效性伊约,因此姚淆,就達(dá)到了檢查迭代器性質(zhì)的作用。

貼出來_Mutable_RandomAccessIteratorConcept的源碼屡律,加以印證:

  template <class _Tp>
  struct _Mutable_RandomAccessIteratorConcept
  {
    void __constraints() {
      __function_requires< _RandomAccessIteratorConcept<_Tp> >();
      __function_requires< _Mutable_BidirectionalIteratorConcept<_Tp> >();
      __i[__n] = *__i;                  // require element access and assignment
    }
    _Tp __i;
    typename std::iterator_traits<_Tp>::difference_type __n;
  };

現(xiàn)在std::__sort之前的那一大坨我們已經(jīng)弄清楚了腌逢,接下來進(jìn)入std::__sort。

std::__sort

接下來std::__sort的實(shí)現(xiàn)和侯捷的《STL源碼剖析》中講的std::sort的實(shí)現(xiàn)基本一樣了超埋。我看的這個(gè)STL版本是gcc 5.3.1使用的版本搏讶,其std::sort的實(shí)現(xiàn)只是在侯捷書中講的版本上套了個(gè)殼子,加了concept checking(也就是前面大費(fèi)周章講的東西)霍殴。如果有這本書的話可以去看這本書媒惕,書上講的詳細(xì)多了,這里只簡單介紹一下實(shí)現(xiàn)来庭。

std::__sort的排序算法是IntroSort和InsertionSort的結(jié)合妒蔚,先說一下什么是IntroSort。

這個(gè)排序算法類似于快排月弛,在快排的基礎(chǔ)上肴盏,當(dāng)遞歸深度超過一定深度(深度為排序元素?cái)?shù)量的對(duì)數(shù)值)時(shí)轉(zhuǎn)為堆排序。偽代碼如下:

procedure sort(A : array):
    let maxdepth = ?log(length(A))? × 2
    introsort(A, maxdepth)

procedure introsort(A, maxdepth):
    n ← length(A)
    p ← partition(A)  // assume this function does pivot selection, p is the final position of the pivot
    if n ≤ 1:
        return  // base case
    else if maxdepth = 0:
        heapsort(A)
    else:
        introsort(A[0:p], maxdepth - 1)
        introsort(A[p+1:n], maxdepth - 1)

接下來貼出來std::__sort源碼帽衙,我把代碼加上了注釋菜皂,不再單獨(dú)解釋代碼了。

 template<typename _RandomAccessIterator, typename _Compare>
    inline void
    __sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
       _Compare __comp)
    {
      if (__first != __last)
    {
      // __introsort_loop先進(jìn)行一遍IntroSort厉萝,但是不是嚴(yán)格意義上的IntroSort恍飘。
      //因?yàn)閳?zhí)行完之后區(qū)間并不是完全有序的榨崩,而是基本有序的。
      //__introsort_loop和IntroSort不同的地方是常侣,__introsort_loop會(huì)在一開始會(huì)判斷區(qū)間的大小蜡饵,當(dāng)區(qū)間小于16的時(shí)候,就直接返回胳施。
      std::__introsort_loop(__first, __last,
                std::__lg(__last - __first) * 2,
                __comp); 
      // 在區(qū)間基本有序的基礎(chǔ)上再做一遍插入排序溯祸,使區(qū)間完全有序
      std::__final_insertion_sort(__first, __last, __comp);
    }
    }

__introsort_loop和__final_insertion_sort代碼:


  enum { _S_threshold = 16 };

  template<typename _RandomAccessIterator, typename _Size, typename _Compare>
    void
    __introsort_loop(_RandomAccessIterator __first,
             _RandomAccessIterator __last,
             _Size __depth_limit, _Compare __comp)
    {
      // 若區(qū)間大小<=16就不再排序。
      while (__last - __first > int(_S_threshold))
    {
      // 若遞歸次數(shù)達(dá)到限制舞肆,就改用堆排序
      if (__depth_limit == 0)
        {
          std::__partial_sort(__first, __last, __last, __comp);
          return;
        }
      --__depth_limit;
      _RandomAccessIterator __cut =
      std::__unguarded_partition_pivot(__first, __last, __comp); // 分割
      std::__introsort_loop(__cut, __last, __depth_limit, __comp); // 右半?yún)^(qū)間遞歸
      __last = __cut;
    // 回到while循環(huán)焦辅,對(duì)左半?yún)^(qū)間進(jìn)行排序,這么做能顯著減少__introsort_loop的調(diào)用的次數(shù)
    }
    }


  template<typename _RandomAccessIterator, typename _Compare>
    void
    __final_insertion_sort(_RandomAccessIterator __first,
               _RandomAccessIterator __last, _Compare __comp)
    {
      if (__last - __first > int(_S_threshold)) // 區(qū)間長度大于16
    {
      // 插入排序
      std::__insertion_sort(__first, __first + int(_S_threshold), __comp); 
      // 也是插入排序椿胯,只是在插入排序的內(nèi)循環(huán)時(shí)筷登,不再判斷邊界條件,因?yàn)橐呀?jīng)保證了區(qū)間前面肯定有比待插入元素更小的元素
      std::__unguarded_insertion_sort(__first + int(_S_threshold), __last, 
                      __comp);
    }
      else // 區(qū)間長度小于等于16的話
    std::__insertion_sort(__first, __last, __comp); // 插入排序
    }

__unguarded_insertion_sort和__insertion_sort和__unguarded_partition_pivot不再贅述哩盲。

總結(jié)

感覺講了一堆廢話前方,C++11中concept checking都不用了,感覺白看了......

Reference

  1. how does __glibcxx_function_requires and __glibcxx_requires_valid_range macros work?
  2. Function pointer to member function
  3. function pointers in c++ : error: must use '.' or '->' to call pointer-to-member function in function
  4. https://en.cppreference.com/w/cpp/iterator
  5. https://en.wikipedia.org/wiki/Introsort
  6. Compile Time Checks
  7. Using Concept Checks
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廉油,一起剝皮案震驚了整個(gè)濱河市惠险,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抒线,老刑警劉巖班巩,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘶炭,居然都是意外死亡抱慌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門眨猎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抑进,“玉大人,你說我怎么就攤上這事睡陪〉ハ唬” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵宝穗,是天一觀的道長。 經(jīng)常有香客問我码秉,道長逮矛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任转砖,我火速辦了婚禮须鼎,結(jié)果婚禮上鲸伴,老公的妹妹穿的比我還像新娘。我一直安慰自己晋控,他們只是感情好汞窗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赡译,像睡著了一般仲吏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝌焚,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天裹唆,我揣著相機(jī)與錄音,去河邊找鬼只洒。 笑死许帐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毕谴。 我是一名探鬼主播成畦,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼涝开!你這毒婦竟也來了循帐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤忠寻,失蹤者是張志新(化名)和其女友劉穎惧浴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奕剃,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衷旅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纵朋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柿顶。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖操软,靈堂內(nèi)的尸體忽然破棺而出嘁锯,到底是詐尸還是另有隱情,我是刑警寧澤聂薪,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布家乘,位于F島的核電站,受9級(jí)特大地震影響藏澳,放射性物質(zhì)發(fā)生泄漏仁锯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一翔悠、第九天 我趴在偏房一處隱蔽的房頂上張望业崖。 院中可真熱鬧野芒,春花似錦、人聲如沸双炕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妇斤。三九已至摇锋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趟济,已是汗流浹背乱投。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顷编,地道東北人戚炫。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像媳纬,于是被迫代替她去往敵國和親双肤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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

  • 青草花相間钮惠,菩提一指來茅糜。 風(fēng)吹花草靜,不知花何明素挽。
    任岸閱讀 143評(píng)論 0 0
  • 香帥 第十五周
    Aero小白閱讀 87評(píng)論 0 0
  • 2017年1月24日晚预明,網(wǎng)友@琳噠是我 在微博發(fā)帖稱缩赛,2011年11月11日,她在麗江游玩時(shí)被一群男子無端毆打撰糠,傷...
    廢宅小魚兒閱讀 555評(píng)論 2 6