STL源碼分析--vector

更多精彩內容,請關注微信公衆號:後端技術小屋c++

1 相關頭文件

stl_vector.h
vector.h
vector

2 內存分配

vector默認使用__default_alloc_template分配內存,該分配器是線程安全的,具體可見STL源碼分析-內存分配後端

3 vector的緩衝區

vector_Vector_base的派生類,_Vector_base有三個成員變量api

protected:
  _Tp* _M_start;
  _Tp* _M_finish;
  _Tp* _M_end_of_storage;

咱們都知道,vector是一種更高級的數組,而數組必然包含一段連續的緩衝區。以下所示,_M_start表示這段緩衝區內數據區的左實邊界,_M_finish表示緩衝區內數據區的右虛邊界,_M_end_of_storage指向內存緩衝區的右虛邊界
vector內存佈局數組

4 vector的迭代器

vector使用連續的物理內存空間,所以迭代器直接使用原始指針表示安全

typedef _Tp value_type;
  
  typedef value_type* iterator;   // vector的迭代器是普通指針
  typedef const value_type* const_iterator;
  
  typedef reverse_iterator<const_iterator> const_reverse_iterator;
  typedef reverse_iterator<iterator> reverse_iterator;

reverse_iterator的定義在stl_iterator.h中,反向迭代器實際上是對正向迭代器的一層封裝。微信

5 vector的API實現

5.1 默認構造函數

例如vector<int> a; 這種狀況下,vector大小爲零,爲了節約內存空間,vector<int>不會主動申請內存、建立緩衝區。函數

explicit vector(const allocator_type& __a = allocator_type())
    : _Base(__a) {}
  
    _Vector_base(const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}

5.2 從size和初始值構造vector

例如vector<int> a(100, 0),這種狀況下,_Vector_base首先使用_Alloc類型的內存分配器分配n*sizeof(Tp)的內存,而後在Vector構造函數中填充初始值源碼分析

vector(size_type __n, const _Tp& __value,
         const allocator_type& __a = allocator_type()) 
    : _Base(__n, __a)
    { _M_finish = uninitialized_fill_n(_M_start, __n, __value); }
    
  explicit vector(size_type __n)
    : _Base(__n, allocator_type())
    { _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); }
    

  _Vector_base(size_t __n, const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) 
  {
    _M_start = _M_allocate(__n);
    _M_finish = _M_start;
    _M_end_of_storage = _M_start + __n;
  }

5.3 複製構造函數

例如佈局

vector<int> a(100, 0); 
vector<int> b(a);     // 複製構造函數

_Vector_base首先使用_Alloc類型的分配器分配n*sizeof(Tp)的內存,而後Vector構造函數將__x的值複製到本地緩衝區中。線程

vector(const vector<_Tp, _Alloc>& __x) 
    : _Base(__x.size(), __x.get_allocator())
    { _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); }

5.4 從迭代器構造vector

例如

vector<int> a(100, 0);
vector<int> b(a.begin(), a.begin() + 10);

注意這裏_Vector_base並無提早申請長度爲last-first的內存,而是在vector::_M_range_initialize中調用_M_allocate來申請內存的。
TODO 注意,這裏須要區分_InputIterator是否爲整數類型

// Check whether it's an integral type.  If so, it's not an iterator.
  template <class _InputIterator>
  vector(_InputIterator __first, _InputIterator __last,
         const allocator_type& __a = allocator_type()) : _Base(__a) {
    typedef typename _Is_integer<_InputIterator>::_Integral _Integral;
    _M_initialize_aux(__first, __last, _Integral());
  }

  template <class _Integer>
  void _M_initialize_aux(_Integer __n, _Integer __value, __true_type) {
    _M_start = _M_allocate(__n);
    _M_end_of_storage = _M_start + __n; 
    _M_finish = uninitialized_fill_n(_M_start, __n, __value);
  }

5.5 析構函數

例如vector<int> a(100, 0);a被回收時,首先調用vector::~vector將包含的全部元素回收,而後調用_Vector_base::~_Vector_base釋放已申請的內存。

~vector() { destroy(_M_start, _M_finish); }
  ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }

5.6 push_back

分兩種狀況,

  • 若是還有可用緩衝區,在緩衝區上執行inplacement new
  • 若是沒有可用緩衝區,從新執行realloc生成新緩衝區, 將舊緩衝區上的數據複製到新緩衝區

代碼細節:
首先判斷是否有空閒內存,若是有,則在空閒內存上執行inplacement new

void push_back(const _Tp& __x) {
    if (_M_finish != _M_end_of_storage) {
      construct(_M_finish, __x);
      ++_M_finish;
    }
    else
      _M_insert_aux(end(), __x);
  }
  

template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
  new ((void*) __p) _T1(__value);
}

若是沒有空閒內存,則執行_M_insert_aux,接着進入else分支,能夠看到,若是push_back以前capacity爲0, 擴展後的capacity爲1,不然新capacity是舊capacity的兩倍。

template <class _Tp, class _Alloc>
void 
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position)
{
  if (_M_finish != _M_end_of_storage) {
    construct(_M_finish, *(_M_finish - 1));
    ++_M_finish;
    copy_backward(__position, _M_finish - 2, _M_finish - 1);
    *__position = _Tp();
  }
  else {
    const size_type __old_size = size();
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
    iterator __new_start = _M_allocate(__len);
    iterator __new_finish = __new_start;
    __STL_TRY {
      __new_finish = uninitialized_copy(_M_start, __position, __new_start);
      construct(__new_finish);
      ++__new_finish;
      __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
    }
    __STL_UNWIND((destroy(__new_start,__new_finish), 
                  _M_deallocate(__new_start,__len)));
    destroy(begin(), end());
    _M_deallocate(_M_start, _M_end_of_storage - _M_start);
    _M_start = __new_start;
    _M_finish = __new_finish;
    _M_end_of_storage = __new_start + __len;
  }
}

內存分配的調用鏈路爲

_M_allocate 
  -> simple_alloc<_Tp, _Alloc>::allocate
    -> _Alloc::allocate (_Alloc默認爲__STL_DEFAULT_ALLOCATOR(_Tp))
      -> allocator<_Tp>
        -> __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>

__default_alloc_template的實現細節見STL源碼分析-內存分配

5.7 其餘

  • begin, 返回數據區左邊界的指針
  • end, 返回數據區右虛邊界指針
  • rbegin, 返回數據區右虛邊界對應的反向迭代器
  • rend, 返回數據區左邊界對應的反向迭代器
  • size, 返回end()-begin(), 即數據區元素個數
  • max_size, 返回size_type類型的最大數, 也就是size_t可以表示的最大值
  • capacity, 返回_M_end_of_storage - begin(); 即緩衝區可以容納的元素個數
  • swap, 交換_M_start_M_finish_M_end_of_storage這三個指針便可
  • insert, 同push_back, 不一樣點在於insert須要移動插入位置以後的全部元素
  • pop_back, 銷燬末尾元素
  • erase, 銷燬指定元素或者區間,而後將被銷燬元素以後的全部元素向頭部複製
  • resize,分兩種狀況
    • 新size比如今大,插入new_size - old_size個0值
    • 新size比如今小,對尾部old_size - new_size個元素執行erase,須要注意的是,resize並不會釋放緩衝區上可用內存
  • operator=, 假設將b賦值給a, 分三種狀況
    • 若是b.size > a.capacity, a先執行_M_allocate_and_copy申請並初始化新緩衝區,再執行_M_deallocate釋放舊緩衝區
    • 若是a.size > b.size, 直接複製b到a, 並銷燬a中多餘的對象
    • 若是a.size < b.size < a.capacity, 先複製b的前a.size個元素到a, 而後執行uninitialized_copy複製b的後b.size-a.size個元素到a中
  • reserve, 申請新內存,反向複製全部元素並釋放舊內存
  • assign(n, val) 實現上也分三種狀況
    • n > a.capacity, 構造新vector(假設爲tmp), 並執行a.swap(tmp)
    • a.size < n < a.capacity, 同operator=
    • n < a.size, 同operator=

推薦閱讀

更多精彩內容,請掃碼關注微信公衆號:後端技術小屋。若是以爲文章對你有幫助的話,請多多分享、轉發、在看。
二維碼

相關文章
相關標籤/搜索