1. 程式人生 > >SGISTL原始碼閱讀八 Vector容器上

SGISTL原始碼閱讀八 Vector容器上

SGISTL原始碼閱讀八 Vector容器上

Vector概述

array是靜態的陣列,一經定義大小不可改變,這對於未知陣列大小的情況來說是非常不利的,如果沒有vector我們只能猜測一個較大的值來初始化陣列。這無疑對空間造成了很大程度上的浪費,而且如果初始值過小,就得必須自己重新定義一個更大的陣列,並且將原來的資料拷貝一份,再釋放掉原來的空間。
vector被稱為動態陣列。隨著元素的加入,它的內部機制會自行擴充空間以容納新元素,它相對於array來說,更加的靈活。


深入Vector原始碼

Vector的迭代器及其資料結構

//預設使用SGISTL空間配置器
template <class T, class Alloc = alloc>
class vector {
public:
  //vector的巢狀型別定義
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type* iterator;			//它的迭代器就是普通指標
  typedef const value_type* const_iterator;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

我們知道vector所維護的是一個連續的線性空間,普通指標就可以作為vector的迭代器。
(在iterator_traits的學習中,我們知道指標的迭代器型別是random_access_iterator。)

protected:
  //...  
  
  iterator start;			//表示目前使用空間的頭
  iterator finish;			//表示目前使用空間的尾
  iterator end_of_storage;	//表示目前可用空間的尾
  
  //...
  
public:
  //由於vector內部的迭代器是保護型別的,所以需要一個對外的介面
  iterator begin() { return start; }
  const_iterator begin() const { return start; }
  iterator end() { return finish; }
  const_iterator end() const { return finish; }
  
  //...
  //返回vector的已使用空間
  size_type size() const { return size_type(end() - begin()); }
  size_type max_size() const { return size_type(-1) / sizeof(T); }
  //返回vector的容量
  size_type capacity() const { return size_type(end_of_storage - begin()); }
  bool empty() const { return begin() == end(); }

這裡我們可以看到vector的結構是由三個迭代器來維護的,分別指向了vector使用空間的頭部,尾部,以及可用空間的尾部。
如圖:

在這裡插入圖片描述

vector的構造與記憶體分配

vector的建構函式
	//預設建構函式,不申請空間
    vector() : start(0), finish(0), end_of_storage(0) {}
    //接受兩個引數,第一個引數 指定vector初始化大小,第二個引數為初始化值(所有空間都會被初始化成value)
    //以下三個是針對不同情況的過載版本,他們都呼叫了fill_initialize函式
    vector(size_type n, const T& value) { fill_initialize(n, value); }
    vector(int n, const T& value) { fill_initialize(n, value); }
    vector(long n, const T& value) { fill_initialize(n, value); }
    
    //接受一個引數,指定vector的初始化大小。
    //詳述
    explicit vector(size_type n) { fill_initialize(n, T()); }
	
    //拷貝建構函式
    vector(const vector<T, Alloc>& x) {
    //呼叫了allocate_and_copy函式
    start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());
    finish = start + (x.end() - x.begin());
    end_of_storage = finish;
    }
    
    //以下兩個建構函式是根據first和last兩個迭代器的範圍來進行初始化
#ifdef __STL_MEMBER_TEMPLATES
    template <class InputIterator>
    vector(InputIterator first, InputIterator last) :
    start(0), finish(0), end_of_storage(0)
    {
    	//呼叫了range_initialize
    	range_initialize(first, last, iterator_category(first));
    }
#else /* __STL_MEMBER_TEMPLATES */
    vector(const_iterator first, const_iterator last) {
        size_type n = 0;
        distance(first, last, n);
        start = allocate_and_copy(n, first, last);
        finish = start + n;
        end_of_storage = finish;
    }

explicit vector(size_type n) { fill_initialize(n, T()); }
這個建構函式接受一個引數,指定了vector的初始化大小。explicit關鍵字是用來防止當只有一個傳入引數時發生隱式轉換的。
例如vector<int> v = 10;,vector<int> v = 'a'
如果沒有explicit關鍵字,以上兩種寫法是正確的。因為在C++中, 如果的建構函式只有一個引數時, 那麼在編譯的時候就會有一個預設的轉換操作:將該建構函式對應資料型別的資料轉換為該類物件. 也就是說 vector<int> v = 10;這段程式碼, 編譯器自動將整型轉換為vector類物件, 實際上等同於下面的操作:

vector<int> temp(10);
vector<int>v = temp;
vector的記憶體分配

vector的建構函式分析中,我們可以看到它們呼叫了fill_initializeallocate_and_copyrange_initialize函式,下面我們就來看一下這些函式的實現。

  • fill_initialize

fill_initializ

//維護了vector的三個迭代器,呼叫了allocate_and_fill函式
void fill_initialize(size_type n, const T& value) {
    start = allocate_and_fill(n, value);
    finish = start + n;
    end_of_storage = finish;
}

allocate_and_fill


protected:
	//typedef simple_alloc<value_type, Alloc> data_allocator;
    //vector給SGISTL的空間配置器設定了一個別名
    iterator allocate_and_fill(size_type n, const T& x) {
        iterator result = data_allocator::allocate(n);
        
        /* __STL_TRY...__STL_UNWIND類似異常處理的try...catch語句塊
		 * 這段程式碼的大意就是,初始化allocate分配的未初始化空間
		 * 如果失敗了,則將分配的記憶體回收,防止記憶體洩露
=		 */
        __STL_TRY {
        	//呼叫初始化uninitialized_fill_n未初始化的空間
            uninitialized_fill_n(result, n, x);
            return result;
        }
        __STL_UNWIND(data_allocator::deallocate(result, n));
    }
  • allocate_and_copy
//allocate_and_copy整體和allocate_and_fill類似,只是他們初始化未初始化空間呼叫的函式不同
#ifdef __STL_MEMBER_TEMPLATES
  template <class ForwardIterator>
  iterator allocate_and_copy(size_type n,
                             ForwardIterator first, ForwardIterator last) {
    iterator result = data_allocator::allocate(n);
    __STL_TRY {
      uninitialized_copy(first, last, result);
      return result;
    }
    __STL_UNWIND(data_allocator::deallocate(result, n));
  }
#else /* __STL_MEMBER_TEMPLATES */
  iterator allocate_and_copy(size_type n,
                             const_iterator first, const_iterator last) {
    iterator result = data_allocator::allocate(n);
    __STL_TRY {
      uninitialized_copy(first, last, result);
      return result;
    }
    __STL_UNWIND(data_allocator::deallocate(result, n));
  }
  • range_initialize
//根據迭代器的不同版本,過載了不同版本
 template <class InputIterator>
  void range_initialize(InputIterator first, InputIterator last,
                        input_iterator_tag) {
    for ( ; first != last; ++first)
      push_back(*first);
  }

  // This function is only called by the constructor.  We have to worry
  //  about resource leaks, but not about maintaining invariants.
  template <class ForwardIterator>
  void range_initialize(ForwardIterator first, ForwardIterator last,
                        forward_iterator_tag) {
    size_type n = 0;
    distance(first, last, n);
    start = allocate_and_copy(n, first, last);
    finish = start + n;
    end_of_storage = finish;
  }

總結

我們介紹了vector的資料結構,構造及記憶體分配的相關內容。
之後我們將繼續介紹vector的相關操作。