(Boolan) C++ STL與泛型編程——容器1

STL標準庫是開發(fā)中的利器放可,也是開發(fā)的寶庫谒臼。

這次的源碼分析主要以GNU C++的2.9和4.9版本為例,因為4.9之后代碼重構耀里,核心部分發(fā)生了巨大的變化蜈缤,再次分別分析一下。

以GCC為例的標準庫位置: ....\x.x.x\include\c++\

本地的目錄

對于2.9和4.9最大的差別其實就是冯挎,2.9主要采用了泛型編程的思想底哥,4.9引入了大量的面向對象編程的思想。

OOP(Object-Oriented Programming 面向對象編程) vs. GP(Generic Programming 泛型編程)

  • OOP
    • OOP主要的思想是將datas和methods關聯(lián)在一起的思想房官。
      也就是數(shù)據放在類中趾徽,操作數(shù)據的方法也是放在類中。(就像我以前舉的一個例子翰守,如果class 貓身上有毛孵奶,那么他必須有一個方法來管理他的毛,也就是舔毛()這個函數(shù)潦俺。只需要貓咪.舔毛();來調用這個函數(shù)拒课,就可以管理和操作對應的數(shù)據)
  • GP
    • GP的主要思想是將datas和methods分開
      在STL中大量使用到了GP的思想徐勃,來實現(xiàn)了數(shù)據和算法的分離,那么早像,算法如何才能操作數(shù)據呢僻肖,這中間的橋梁就是Iterators(迭代器)了,通過Iterator卢鹦,算法可以從容器中獲取到需要的數(shù)據臀脏,同樣也就可以起到操作數(shù)據的目的。
      為何STL會采用GP的思想呢冀自?其實使用了GP思想揉稚,類和類之間的關系不會那么緊密,也就不會產生很強的耦合性熬粗,便于不同的成員搀玖,協(xié)同開發(fā)不同的模塊,有助于加快項目開發(fā)得效率驻呐,大家只需要依據“中間商”Iterator來編寫各自代碼就行了灌诅。

對于OOP來說最好的一點就是,方法和數(shù)據在同一個類中含末,那么方法是專門為類所設計的猜拾。比較方便能夠管理其中的數(shù)據。GP由于數(shù)據和方法分離佣盒,操作的時候挎袜,難免有些數(shù)據,不能被這個方法所操作肥惭。比如盯仪,list 不能使用::sort() 進行排序,那到底是為什么呢务豺?

  • 看看::sort()的源碼磨总,發(fā)現(xiàn)問題所在
template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last)
{
    if(first != last)
    {
        _introsort_loop(first, last, value_type(first), __lg(last-first)*2);
        __final_insertion_sort(first, last);
    }
}
.....
template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessterator first, RandomAccessIterator last, T*, Size depth_limit)
{
    ......
    RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first)/2), *(last - 1))));
//由于此處牽扯到了Iterator的下標運算
//list不是一個連續(xù)空間嗦明,前后節(jié)點之間靠指針相連笼沥,所以list 的Iterator不具備下表直接運算的能力,所以娶牌,list不能直接使用::sort()來進行排序
//也正是由于這個原因::sort() 只能為RandomAccessIterator來進行排序
    ......
}
  • 那既然如此奔浅,在STL中,難道數(shù)據不適合就不能使用了诗良,是否有其他方式來使用呢汹桦?
    • 以max()為例
//標準庫中的兩個函數(shù)
template<class T>
inline const T& max(const T& a, const T& b){
    return a < b ? b: a;
}

template<class T, class Compare>
inline const T& max(const T& a, const T& b, Compare comp){
    return comp(a, b)? b: a;
}
//如何使用
//定義一個依據長度比較大小的函數(shù)
bool strLonger(const T& a, const T& b){
    return a.size() < s2.size();
}
cout << "max of zoo and hello:" 
  << max(string("zoo"), string("hello")) << endl;

cout << "longer of zoo and hello: " 
   << max(string("zoo"), string("hello"), strLonger) << endl;

分配器

分配器是容器管理內存的工具,對于容器的效率起著比較重要的作用
在正式開始說allocator之前鉴裹,先說幾句operator new()和 malloc()以及operator delete() 和free()
在創(chuàng)建對象時舞骆,會調用operator new()钥弯,而operator new()中分配內存實際也還是調用有C語言的Runtime Library所提供的malloc(),再由系統(tǒng)來分配所需要的內存督禽;銷毀對象時脆霎,則會使用operator delete(),而他實際會調用free()狈惫。

  • vc中的operator new()
void *operator new (size_t size, const std::nothrow_t&)
{
    void *p;
    while((p = malloc(size)) == 0)
    {
        _TRY_BEGIN
        if(_callnewh(size) == 0) break;
        _CATCH(std::bad_alloc) return(0);
        _CATCH_END
    }
    return (p);
}
malloc所分配的內存圖

malloc所分配的內存圖睛蛛,如上圖所示,其中藍色部分為真正需要的內存胧谈。其余部分為系統(tǒng)分配的管理這部分空間的配套內存忆肾,其中保存了需要的這塊內存的相關信息
灰色部分為調試模式系統(tǒng)分配的內存空間

根據vc版本,容器主要的使用的是allocator這個分配器的情況

template<class _Ty, class _a = allocator<_Ty> >
class vector{....}
template<class _Ty, class _a = allocator<_Ty> >
class list{....}
template<class _Ty, class _a = allocator<_Ty> >
class deque{....}
template<class _Ty, class _a = allocator<_Ty> >
class set{....}
template <class _Ty>
class allocator{
public:
    typedef _SIZT size_type;
    typedef _PDFT difference_type;
    typedef _Ty _FARQ *pointer;
    typedef _Ty value_type;
    pointer allocate(size_type _N, const void *){return (_Allocate((difference_type)_N, (pointer)0));  }
    void deallocate(void _FAQ *_P, size_type){operator delete(_P); }
}

///.....
//其中_Allocate()如下:
template<class _Ty> inline
_Ty _FARQ*_Allocate(_PDFT _N, _FARQ *){
    if (_N < 0) _N = 0;
    return (( _Ty _FARQ*) operator new ((_SIZT) _N * sizeof(_Ty)));
}
//如果使用allocator來申請內存
int *p = allocator<int>.allocate(512, (int*)0);  //申請空間
allocator<int>().dellocate(p, 512);//釋放空間

由源代碼可以看出菱肖,VC分配器實際是通過operator new和delete來調用malloc和free來管理元素的內存

  • GNU2.9的allocator也沒有過多的設計客冈,依然是通過::operator new和::operator delete來完成allocate()和deallocate(),但是稳强,在2.9版本中郊酒,實際容器使用的并非allocator,而是alloc
template<class _Ty, class _a = alloc >
class vector{....}
template<class _Ty, class _a = alloc >
class list{....}
template<class _Ty, class _a = alloc >
class deque{....}
template<class _Ty, class _a = alloc >
class set{....}
  • alloc這個分配器的主要目的是為了減少malloc的調用次數(shù)
    malloc申請空間時键袱,多余的空間的主要目的是為了free時能夠快速的知道申請的空間到底是多大燎窘。而對于容器來說,其中所保存的元素大小是相同的蹄咖,不需要在每個元素的前頭都記錄空間到底是多大褐健。
  • alloc的解決方案:
alloc的管理方式示意圖
  • 設計了十六條鏈表,每條鏈表都負責對應大小的管理工作
  • 元素的大小會被調整到8的倍數(shù)澜汤,然后在管理蚜迅,比如,50字節(jié)會被調整為56字節(jié)
  • 第一條鏈表負責8個字節(jié)大小元素的部分俊抵,第二條鏈表負責16個字節(jié)大小元素的部分谁不,第三條負責24個字節(jié)大小元素的部分,以此類推徽诲,一直到第十六條鏈表刹帕,負責管理128字節(jié)的元素的部分
  • 如果沒有內存,則一次性申請較大的空間谎替,然后將這些空間等分偷溺,所以相對于只malloc一次,則只有大空間具有那些額外的空間钱贯,而中間等分的部分實際上沒有那么多額外的空間的浪費

那么對于GNU4.5之后還在使用alloc這個分配器嗎挫掏?

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector: protected _Vector_base<_Tp, _Alloc>{....}
#define __allocator_base __gnu_cxx::new_allocator
template<typename _Tp>
class allocator: public __allocator_base<_Tp>
{
 .....
}
template<typename _Tp>
class new_allocator
{
    ...
    pointer allocator(size_type __n, const void* = 0){
        if(__n > this ->max_size())
            std::__throw_bad_alloc();
        return static_cast<_Tp*> (::operator new (_n * sizeof(_Tp)));
}
    void deallocate(pointer __p, size_type){
        ::operator delete(__p);
}
    ...
}
分配器的UML
  • 在4.9版本以后,gnu的分配器也沒有特殊設計秩命,也是采用直接調用operator new來分配空間
  • 之前設計的分配器被放入到了擴充分配器中(extention allocators)尉共,其中__pool_alloc就是GNU2.9的alloc褒傅,可以

vector<string,__gun::_cxx::__pool_alloc<string> > vec;來使用

容器結構分類

  • 序列式容器(Sequence Container)的衍生關系

    • array (C++2.0)連續(xù)空間
    • vector 連續(xù)空間
    • heap 以算法形式呈現(xiàn)(xxx_heap())
      • priority_queue
    • list 雙向鏈表
    • slist C++2.0中為forward_list,單向鏈表
    • deque 分段連續(xù)空間
      • stack Container Adapter
      • queue Container Adapter
  • 關聯(lián)式容器(Associative Containers)的衍生關系(復合)

    • rb_tree 紅黑樹,非公開
      • set
      • map
      • multiset
      • multimap
    • hashtable非公開
      • hash_set非標準袄友,C++2.0為unordered_set
      • hash_map非標準樊卓,C++2.0為unordered_map
      • hash_multiset非標準,C++2.0為unordered_multiset
      • hash_mulitmap非標準杠河,C++2.0為unordered_multimap

容器 list

template <class T>
struct __list_node{
    typedef void* void_pointer;
    void_pointer prev;
    void_pointer next;
    T data;
};

template<class T, class Alloc = alloc>
class list{
protected:
    typedef __list_node<T> list_node;
public:
    typedef list_node* link_type;
    typedef __list_iterator<T, T&, T*> iterator;
protected:
    link_type node;
};

template<class T, class Ref, class Ptr>
struct __list_iterator{
    typedef T value_type;
    typedef Ptr pointer;
    typedef Ref reference;
}
UML
內存關系示意圖
  • list為一個循環(huán)鏈表(如圖)碌尔,但是對于迭代器來說,end()獲取到的并非容器中的最后一個元素券敌,而應該是唾戚,最后一個元素之后的空元素,所以在list實現(xiàn)的時待诅,可以看到叹坦,end()指向了一個灰色的區(qū)域,這個區(qū)域實際就是end()指向的非容器內元素的區(qū)域
  • 由于list非連續(xù)空間卑雁,所以Iterator在++時募书,如果不作調整,不會默認的移動到下一個不連續(xù)空間测蹲,所以莹捡,為了讓Iterator能夠和指針的用法相似,Iterator一定是一個class
template<class T, class Ref, class Ptr>
struct __list_iterator{
    typedef __list_iterator(T, Ref, Ptr> self;
    typedef bidirectional_iterator_tag iterator_category;
    typedef T  value_type;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef __list_node<T>* link_type;
    typedef  ptrdiff_t difference_type;

    link_type nod;

    reference operator*() const{
        return (*node).data;
    }
    pointer operator->() const {
        return &(operator*());
    }
    self& operator++(){//前++
        node = (link_type)((*node).next); return *this;
    }
    self operator++(int){//后++扣甲,參數(shù)實際無意義
        self temp = *this; ++*this; return tmp;
    }
};
4.9版本list的UML

Iterator的設計原則

算法要求這幾項的類型必須指定出來
  • 算法(algorithms)在操作容器(Container)中的數(shù)據需要通過Iterator知道的信息如下:
    1. iterator_category:Iterator的性質篮赢,例如是否可以雙向查詢
    • difference_type:兩個Iterator之間的距離的type(int、unsigned int)琉挖,決定了容器可以容納多少元素
    • value_type:元素本身的type
    • reference:引用
    • pointer:指針
      在Iterator的設計時启泣,必須有這五種associated types
  • traits的引入
    • 如果Iterator不是一個class的情況,如果這樣的情況示辈,無法從一個指針中獲取以上的幾種類型寥茫,那么這時候,需要一個“中介”來去協(xié)調這件事矾麻,這時候就出現(xiàn)了一個traits的機制
    • 這個traits可以區(qū)分到底是class設計的Iterator纱耻,也能夠區(qū)分是指針傳入的Iterator
//traits的設計
template<class I>
struct iterator_traits{
    typedef typename I::value_type value_type;
    typedef typename I::iterator_category
    typedef typename I::difference_type
    typedef typename I::pointer
    typedef typename I::reference
};

//針對指針的兩種偏特化
template<class T>
struct iterator_traits<T*>{
    typedef T value_type;
    typedef random_access_iterator_tag iterator_category;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
};

template <class T>
struct iterator_traits<const T*>{
    typedef T value_type;
    typedef random_access_iterator_tag iterator_category;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
}
//traits的使用
template<typename I, ....>
void algorithm(......){
    typename iterator_traits<I>::value_type v1;
}
  • 根據偏特化,如果傳入的為指針就會自動進入偏特化的部分射富,那么就根據偏特化來獲取響應信息

  • 各式各樣的traits以及對應的頭文件

    • type traits : .../c++/type_traits
    • iterator traits: .../c++/bits/stl_iterator.h
    • char traits: .../c++/bits/char_traits.h
    • allocator traits:.../c++/bits/alloc_traits.h
    • pointer traits: .../c++/bits/ptr_traits.h
    • array traits:.../c++/bits/array.h

容器Vector

  • vector根據三個指針就可以控制全部內容 iterator start;膝迎、 iterator finish;、iterator end_of_storage;
    其中finish指向最后一個元素之后的位置胰耗。
template <class T, class Alloc = alloc>
class vector
{
public:
    typedef  T value_type;
    typedef value_type* iterator;
    typedef value_tyle&  reference;
    typedef size_t  size_type;
protected:
    iterator start;
    iterator finish;
    iterator end_of_storage;
public:
    iterator begin(){return start;}
    iterator end() {return finish;}
    size_type size() const{
        return size_type(end() - begin());
    }
    size_type capacity() const {
        return size_type(end_of_storage - begin());
    }
    bool empty() const {
      return begin() == end();
    }
    reference operator[](size_type n){return *(begin() + n); }
    reference front() {return *begin();}
    reference back(){ return *(end() - 1); }
}
  • 二倍成長
    • 對于內存來說沒辦法實現(xiàn)原地擴充,因為前后都可能存在著其他程序的數(shù)據芒涡,如果擴充柴灯,意味著會要影響到其他程序卖漫,并且操作系統(tǒng)也不允許這樣干。那么對于vector來說赠群,hi如何來實現(xiàn)擴充的呢羊始?那么再擴充的時候,需要在內存的其他區(qū)域找到空間查描,在新找到的空間進行擴充完成后突委,再將數(shù)據復制到新開辟的空間中。而且每次增長的空間都是以兩倍作為基準冬三。


      vector的內存圖

存入元素和兩杯增長的代碼

void push_back()(const T& x)
{
    if(finish != end_of_storage){//尚有備用空間
        construct(finish, x); 
        ++finish; 
    }
    else{
        insert_aux(end(), x);
    }
}


template<class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x){
    if(finish != end_of_storage){//空間夠用
        //在備用空間起始處建一個元素匀油,并以vector最后一個元素為其初值
        construct(finish, *(finish - 1);
        ++finish;
        T x_copy = x;
        copy_backward(postion, finish - 2, finish - 1);
        *postion = x_copy;
    }
    else{  //空間不夠用
        const size_type old_size = size();
        const size_type len = old_size != 0? 2*old_size: 1;
        iterator new_start = data_allocator::allocate(len);
        //以上分配原則:剮原大小為0,分配1勾笆;不為0敌蚜,分配原大小的兩倍;前半段用來放置原數(shù)據窝爪,后半段用來放置新數(shù)據
        iterator new_finish = new start;
        try{
            //將原vector的內容拷貝到新的vector
            new_finish = uninitialized_copy(start, position, new_start);
            construnct(new_finish, x);//為新元素設置初值x
            ++new_finish;
            //拷貝安插點后的原內容
            new_finish = uninitialized_copy(postion, finish, new_finish);
        }
        catch(...){
              destory(new_start, new_finish);
            data_allocator::deallocate(new_start, len);
          throwl
        }
        //析構并釋放元vector
        destory(begin(), end());
        //調整迭代器弛车,指向新的vector
        deallocate();
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
    }
}
  • GNU4.9之后的結構
UML

容器array

  • 沒有ctor,沒有dtor
template<typename _Tp, std::size_t _Nm>
struct array{
    typedef _Tp;
    typedef _Tp*;
    typedef value_type*;
  
    value_type _M_instance[_Nm? _Nm: 1];
    iterator begin(){
        return iterator(&_M_instance[0]);
    }
    iterator end(){
        return iterator(&_M_instance[_Nm]);
    }
}

forward_list

  • 單向鏈表蒲每,具體可以參考list(雙向鏈表)
UML
內存示意圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纷跛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子邀杏,更是在濱河造成了極大的恐慌忽舟,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淮阐,死亡現(xiàn)場離奇詭異叮阅,居然都是意外死亡,警方通過查閱死者的電腦和手機泣特,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門浩姥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人状您,你說我怎么就攤上這事勒叠。” “怎么了膏孟?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵眯分,是天一觀的道長。 經常有香客問我柒桑,道長弊决,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮飘诗,結果婚禮上与倡,老公的妹妹穿的比我還像新娘。我一直安慰自己昆稿,他們只是感情好纺座,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溉潭,像睡著了一般净响。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喳瓣,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天馋贤,我揣著相機與錄音,去河邊找鬼夫椭。 笑死掸掸,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蹭秋。 我是一名探鬼主播扰付,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仁讨!你這毒婦竟也來了羽莺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洞豁,失蹤者是張志新(化名)和其女友劉穎盐固,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丈挟,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡刁卜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了曙咽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛔趴。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡疏叨,死狀恐怖谢揪,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情著恩,我是刑警寧澤洒嗤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布箫荡,位于F島的核電站,受9級特大地震影響渔隶,放射性物質發(fā)生泄漏羔挡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婉弹。 院中可真熱鬧睬魂,春花似錦终吼、人聲如沸镀赌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽商佛。三九已至,卻和暖如春姆打,著一層夾襖步出監(jiān)牢的瞬間良姆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工幔戏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玛追,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓闲延,卻偏偏與公主長得像痊剖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子垒玲,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容