1. 程式人生 > >《STL原始碼剖析》閱讀筆記

《STL原始碼剖析》閱讀筆記

1.空間的配置與釋放

物件構造前的空間配置和物件析構後的空間釋放,由<stl_alloc.h>負責,SGI對此的設計哲學:

  • 向system heap要空間
  • 考慮多執行緒狀態
  • 考慮記憶體不足時的應變措施
  • 考慮過多“小型區塊”可能造成的記憶體碎片(fragment)問題
考慮到小型區塊可能造成的記憶體碎片問題,SGI設計了雙層級配置器(通過開啟一個識別符號,來確定是哪個級別的)
  • 第一級配置器直接使用malloc() 和free()
  • 第二級配置器則視情況採用不同的策略,當配置區塊超過128bytes時,視之為“足夠大”,便呼叫第一級配置器;當配置塊小於128bytes時,認為其“過小”,為了降低overhead,採用memory pool(記憶體池)的方式來正例,而不再求助於第一級配置器。

第一級配置器中的allocate()直接呼叫malloc(),deallocate()直接呼叫free(), reallocate()直接呼叫realloc()來申請和回收空間

第二級別的做法是,如果區域足夠大(超過128bytes時),就交由第一級別配置器處理,否則以記憶體池管理:每次配置一大塊記憶體,並維護對應的自由連結串列(free-list)。下次如果有相同大小的記憶體需求,就直接從free-lists中取,如果釋放,就由配置器回收到free-list中,free-list中的塊的大小通常為8的倍數。但維護不同指向多個連結串列的指標也會浪費空間。

第二級的allocate函式做法:如果申請的空間大於128bytes,那麼直接呼叫第一級配置器,小於128bytes就檢查對應的free list,如果free list中有,直接分配;否則呼叫refill()函式;deallocate的做法:該函式首先判斷區塊大小,大於128bytes就呼叫第一級配置器,否則找到對應的free list,然後將區塊收回。

refill()函式通過呼叫chunk_alloc()從記憶體池中獲取一塊空間(記憶體池如何管理/)

2.vector

vector中有三個重要的迭代器:start, finish, end_of_storage

start:表示目前使用空間的頭

finish:表示目前使用空間的尾

end_of_storage:表示目前可用空間的尾

其中的很多操作如begin(), end(), size(), capacity(), empty(), operator[]等都是通過這三個指標來操作

事實上:迭代器就是普通的指標 vector<int> a; a的迭代器就是int *指標

vector的內部就是陣列,初始化時,會分配一個預設大小的capacity,但push_back()向vector中插入元素時,如果當時的容量不夠,那麼容量會擴充至兩倍。容量的擴充過程中,可能需要重新分配一塊更大的空間,元素移動,並釋放原來的空間。

vector預設使用alloc作為分配器。

注意:對vector的任意操作,如果引起空間的重新配置,那麼指向原來vector的迭代器就會失效,務必注意。

3.關聯容器

所謂關聯容器,就是每個資料元素是key+value,當元素插入到關聯容器時,根據key,以某種特定規則將元素放置於適當位置,關聯容器沒有所謂的頭尾,不能進行push_back(), push_front(), pop_back(), pop_front()等操作 

通常關聯容器的內部結構是一個balanced binary tree(平衡二叉樹),為的是高效率的操作(lgN),平衡二叉樹有很多種:AVL-tree、RB-tree等,STL中的map, set, multimap, multiset內部採用RB-tree實現。

值得注意的是:我們在討論紅黑樹之前,首先要記住:紅黑樹是一個二叉查詢樹,具備二叉查詢樹的所有性質

除了二叉查詢樹的性質,紅黑樹還有的五個基本性質:

  1. 每個節點要麼為紅要麼為黑
  2. 根節點為黑
  3. 葉子節點黑
  4. 如果一個節點為紅,那麼其子節點必須為黑
  5. 從根節點到葉子節點的每條路徑上的黑節點數相同,即黑高相同

當在對紅黑樹進行插入和刪除等操作時,對樹做了修改可能會破壞紅黑樹的性質。為了繼續保持紅黑樹的性質,可以通過對結點進行重新著色,以及對樹進行相關的旋轉操作,即通過修改樹中某些結點的顏色及指標結構,來達到對紅黑樹進行插入或刪除結點等操作後繼續保持它的性質或平衡的目的。

紅黑樹的調整的大致思想分兩步:

  • 首先,將一個節點插入獲刪除,需要保證二叉查詢樹的性質
  • 其次,為了保證平衡性,就需要左旋或者右旋轉,保證黑高相同
  • 最後,當一個節點已經插入或者刪除以後,還需要修改節點的紅黑性來保持紅黑樹的基本性質,需要看父節點的紅黑性
4.set和map set中所有元素會根據key進行排序,注意不能通過迭代器更改set的key,當用戶對set中的元素進行insert獲erase後,迭代器不會失效(是由其內部結構決定的) map的元素是pair型別(#include <utility>),key+val,並根據key進行自動排序,map與set一樣,不能通過迭代器改變pair中的key值,因為map元素的key關係到map元素的排列規則,任意改變key會嚴重破壞map的組織。但科一改變value的值。 當然map在執行insert和erase後,迭代器不會失效 5.List 每一個設計過list的人都知道,list本身和list的節點是不同的結構,需要分開設計。 STL list 是一個雙向連結串列,迭代器必須具備前移、後移的能力。 list是一個環狀雙向連結串列,所以它只需要一個指標,便科一完整表現整個連結串列 list有一個重要性質:insert和接合操作都不會造成原有的list迭代器失效,刪除一個迭代器不影響其他迭代器。 list類中只有一個指標node,有一個空白節點,如果讓node指向刻意置於尾端的一個空白節點,node便能符合STL對於“前閉後開”區間的要求,成為last迭代器
<span style="font-family:Microsoft YaHei;font-size:14px;">iterator begin() {
    return (link_type)(*node).next;
}
iterator end() {
    return node;
}</span>
list 預設使用alloc作為空間配置器,並據此另外定義一個list_node_allocator,為的是方便地以節點大小為配置單位。 list的操作很多 clear()清除所有節點,insert(pos, value)插入節點, push_front(x)插入一個節點作為頭節點,push_back()插入一個節點作為尾節點,erase(itr)刪除一個位置itr的節點。pop_front()移除頭節點,pop_back()移除尾節點 remove(value):將數值為value的所有元素移除 unique();移除數值相同的連續元素,注意,只有“連續而相同的元素”,才會被移除剩下一個 splice(const_iterator pos, list &other): splice(const_iterator pos, list &other, const_iterator first, const_iterator last); 將某連續範圍的元素從一個list移動到另一個list的某個定點。 merge(list &other):將x合併到*this身上,兩個lists的內容都必須先經過遞增排序 reverse():將*this的內容逆向重置 sort():list不能使用STL演算法sort(),必須使用自己的sort() sort()使用quick sort實現,如何實現? 6.deque

deque是一種雙向開口的連續線性空間,即可以在頭尾兩端分別做元素的插入和刪除操作。vector雖然也可以進行pop_front, push_front,但是其效率非常低。

vector與deque的差別:1.deque允許o(1)時間對頭進行插入和移除;2.deque沒有所謂容量(capacity)觀念,因為它是動態地以分段連續空間組合而成,隨時可以增加一些新的空間並連結起來。

deque由一段一段的定量連續空間構成,一旦有必要在deque的前端或尾端增加新空間、便配置一段定量連續空間,串接在整個deque的頭端或尾端。

deque採用一塊所謂的map(注意:不是STL的map容器)作為主控,稱為中控器,這裡所謂的map是一塊連續空間,其中每個元素都是指標,指向一段連續線性空間,稱為緩衝區,緩衝區才是deque的儲存空間主體。 deque是分段連續空間,維持其“整體連續”假象的任務,落在了迭代器的operator++和operator--兩個運算身上。