STL源碼剖析(一)
SLT簡介
STL(Standard Template Library),即標準模板庫,是一個高效的C++程序庫。包含了諸多在計算機科學領域裏常用的基本數據結構和基本算法。為廣大C++程序員們提供了一個可擴展的應用框架,高度體現了軟件的可復用性。其核心思想就是泛化編程(generic programming),在這種思想裏,大部分基本算法被抽象,被泛化,獨立於與之對應的數據結構,用於以相同或相近的方式處理各種不同情形。
STL組件
STL中包含了6大組件
- 容器(Containers):包含各種基礎的數據結構,如vector, list, deque, set, map等。
- 分配器(Allocators):負責空間配置與管理。
- 算法(Algorithms):各種常用的算法,如sort, search, copy, erase等等。
- 叠代器(Iterators):負責連接Containers與Algorithms。
- 適配器(Adapters):可以用來修飾Containers,Iterators和Functors接口的組件。
- 函數式(Functors):類似於函數,可以作為Algorithms的一種策略。
六大組件的關系
Containers 通過 Allocators 取得數據存儲空間,Algorithms 通過 Iterators 存取 Containers 內容,Functors 可以協助 Algorithms 完成不同的策略變,Adapters 可以修飾或套接Containers,Iterators和Functors。
容器
結構與分類
容器總體上分為三大類:
- Sequence Containers(序列容器): Arrary(大小固定,無法自動擴充), Vector(只可向後擴充, 兩倍的擴展), Deque(可向前或向後擴充, 分段連續, stack和queue都是基於此 ), List(雙向鏈表), Forwaed-List(單向鏈表)
- Associative Containers(關聯容器):Set/Multiset, Map/Multimap(基本都用紅黑樹來實現)
- Unordered Containers(無序容器): Unordered Set/Multiset, Unorder ed Map/Multimap(基本都是 HashTable Separate Chaining 實現)
Array
是一種固定大小的容器類型,在定義的時候就要聲明大小和類型。Array其實就是對C語言中數組的一種擴充升級,使其支持了叠代器的操作,便於STL算法的使用。array在使用和性能上都要強於內置數組,對於一些固定大小的使用場景,可以用array來替代原先數組的工作。
TR1版本源碼如下:
template<typename _Tp, std::size_t _Nm>
struct array
{
typedef _Tp value_type;
typedef _Tp* pointer;
typedef balue_type* iterator;
value_type _M_instance[_Nm ? _Nm : 1];
iterator begin()
{ return iterator(&_M_instance[0]);}
iterator end()
{ return iteratoe(&_M_instance[_Nm]);}
...
}
Vector
Vector 使用起來和一個數組十分相似,但是在空間用完時,可以自動擴充自己的空間。一般而言空間的擴充,無法在原地完成擴充。所以會在內存中新申請一片內存(通常都是之前空間大小的2倍大),然後通過拷貝將原有數據拷貝到新的地址空間。
Vector中存在三個指針來表明Vector:
- T* start:指向第一個元素的地址
- T* finish:指向目前最後一個地址之後的一個空間的地址
- T* end_of_storage:指向當前Vector的最後一個空間地址
需要註意的是:在空間(兩倍)增長的過程中涉及到了大量的拷貝構造和析構!
List
相較於vector的連續線性空間,List就顯得復雜許多,它的好處是每次插入或刪除一個元素,就配置或釋放一個元素空間。因此,list對於空間的運用有絕對的精準,一點也不浪費。而且,對於任何位置的元素插入或元素移除,List永遠是常數時間。
List不僅是一個雙向鏈表,而且還是一個環狀雙向鏈表。 另外,還有一個重要性質,插入操作和接合操作都不會造成原有的List叠代器失效,這在Vector是不成立的。因為Vector的插入操作可能造成空間的重新配置,導致原有的叠代器全部失效。甚至List的元素刪除操作(erase),也只有“指向被刪除元素”的那個叠代器失效,其他叠代器不受任何影響。
Forward-List
Forward-List容器與List容器的主要設計區別是List保持內部唯一的一個鏈接到下一個元素,而後者則保持每個元素的兩個鏈接:一個指向下一個元素和一個前一個。允許高效在兩個方向叠代,但每個元素的消耗額外的存儲空間,並輕微較高的時間開銷插入和刪除元素的叠代。Forward-List對象,從而比List對象更有效率,雖然他們只能向前遍歷。
所以Forward-List的一個最大的缺點就是無法直接訪問指定位置上元素,每次一的訪問都需要從頭開始訪問,這樣的操作需要線型的時間。
Deque
可以向兩端擴充,通過指針連接不同分段的空間,模擬出連續空間。
template <class T, class Alloc=alloc, size_t BufSiz=0>
class deque{
public:
typedef T value_type;
typedef __deque_iterator<T,T&,T*,BufSiz> iterator;
protected:
typedef pointer* map_pointer;//T**
protected:
iterator start;
iterator finish;
map_pointer map;
size_type map_size;
public:
iterator begin(){return start;}
iterator end(){return finish;}
size_type size(){return finish-start;}
...
}
template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer;
typedef __deque_iterator self;
T* cur;
T* first;
T* last;
map_pointer node;
...
}
start指向第一塊分區,finishi指向最後一塊分區,map是用來存放各個分區的地址(vector實現),map_size是map的大小。
start和finish作為iterator,cur指向當前的元素,first指向第一個存放的元素,last指向當前分區中最後一個存放的數據之後的位置,node指回map。
deque 如何模擬連續空間?
基本全部依靠deque iterators完成
reference operator*() const
{
return *cur;
}
pointer operator->() const
{
return &(operator*());
}
difference_type operator-(const self& x) const
{
return difference_type(buff_size()) * (node - x.node - 1) + (cur - first) + (x.last - x.cur);
}
self& operator++(){
++cur;
if(cur == last){
set_node(node + 1);
cur = first;
}
return *this;
}
self operator++(int){
self tmp = *this;
++*this;
return tmp;
}
self& operator--(){
if(cur == first){
set_node(node - 1);
cur = last;
}
--cur;
return *this;
}
self operator--(int){
self tmp = *this;
--*this;
return tmp;
}
void set_node(map_pointer new_node){
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}
self& operator+=(difference_type n){
difference_type offset = n + (cur - first);
if(offset >= 0 && offset < difference_type(buffer_size())){
cur += n;
}
else{
difference_type node_offset = offset > 0 ? offset / difference_type(buffer_size()) : -difference_type((-offset - 1) / buffer_size()) - 1;
set_node(node + node_offset);
cur = first + (offset - node_offset * difference_type(buffer_size()));
}
return * this;
}
self operator+(difference_type n) const {
self tmp = *thisl
return tmp +=n;
}
self& operator-=(fifference_type n)
{
return *this += -n;
}
self operator-(difference_type n) const
{
self tmp = * this;
return tmp -= n;
}
reference operator[](difference_type n) const
{
return *(*this + n);
}
STL源碼剖析(一)