1. 程式人生 > >STL源碼剖析(一)

STL源碼剖析(一)

都是 雙向 last generic 線性空間 圖片 有效 separate pre

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源碼剖析(一)