STL源碼分析--deque

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

1 deque相關頭文件

deque
deque.h
stl_deque.h

2 deque的數據結構

deque爲雙向隊列,同時支持從隊首和隊尾插入和彈出值。其數據結構分爲兩部分,以下圖所示:c++

  1. 連續緩衝區_M_map, 元素類型爲——Tp*。若是元素值不爲NULL,則指向某個內存塊;deque中使用__nstart(二級指針類型__Tp**)指向第一個內存塊對應的數組元素;__nfinish指向最後一個內存塊對應的數組元素
  2. 一系列定長(512 Byte)的內存塊,又可稱爲節點(node)。

deque的數據結構

代碼以下,_Tp表示deque中元素類型,_Alloc爲內存分配器類型。_M_map_size表示當前連續緩衝區_M_map的長度,即最多能容納多少個node。迭代器_M_start指向deque的左確界,對應隊首值。迭代器_M_finish指向deque的右虛界。後端

template <class _Tp, class _Alloc>
class _Deque_base {
  ...

protected:
  _Tp** _M_map;
  size_t _M_map_size;  
  iterator _M_start;
  iterator _M_finish;
  ...
}

3 deque的迭代器

deque的迭代器屬於random_access_iterator, 支持隨機訪問
其中_M_cur指向當前內存塊node中當前值的位置,_M_first指向當前內存塊node的首地址,_M_last指向當前內存塊的虛邊界數組

template <class _Tp, class _Ref, class _Ptr>
struct _Deque_iterator {
  typedef _Deque_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
  static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }
  
  ...
  typedef _Tp** _Map_pointer;
  ... 
  
  _Tp* _M_cur;
  _Tp* _M_first;
  _Tp* _M_last;
  _Map_pointer _M_node;

  _Deque_iterator(_Tp* __x, _Map_pointer __y) 
    : _M_cur(__x), _M_first(*__y),
      _M_last(*__y + _S_buffer_size()), _M_node(__y) {}

3.1 構造

迭代器的構造函數中,傳入queue中_M_map中元素地址__y,和指向內存塊node中值的地址__x,初始化_M_cur, _M_first, _M_last_M_node微信

template <class _Tp, class _Ref, class _Ptr>
struct _Deque_iterator {
  typedef _Deque_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
  static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }
  
  ...
  typedef _Tp** _Map_pointer;
  ... 
  
  _Tp* _M_cur;
  _Tp* _M_first;
  _Tp* _M_last;
  _Map_pointer _M_node;

  _Deque_iterator(_Tp* __x, _Map_pointer __y) 
    : _M_cur(__x), _M_first(*__y),
      _M_last(*__y + _S_buffer_size()), _M_node(__y) {}

3.2 operator++

首先判斷迭代器當前位置是否爲node尾部,若是是,則跳轉至右相鄰node頭部,不然_M_cur自增1數據結構

代碼以下,其中_M_set_node從新指定iterator當前內存塊爲_M_node+1,重置_M_first_M_lastdom

_Self& operator++() {
    ++_M_cur;
    if (_M_cur == _M_last) {
      _M_set_node(_M_node + 1);
      _M_cur = _M_first;
    }
    return *this; 
  }  

  void _M_set_node(_Map_pointer __new_node) {
    _M_node = __new_node;
    _M_first = *__new_node;
    _M_last = _M_first + difference_type(_S_buffer_size());

3.3 operator--

首先判斷迭代器當前位置是否在當前node頭部,若是是,則跳轉至左相鄰node的頭部,不然_M_curr自減1函數

_Self& operator--() {
    if (_M_cur == _M_first) {
      _M_set_node(_M_node - 1);
      _M_cur = _M_last;
    }
    --_M_cur;
    return *this;
  }

3.4 operator+=

計算目標位置相對於本node頭部的偏移量,判斷:源碼分析

  • 偏移量 < node大小,說明目標位置在本node內,_M_curr自增n
  • 偏移量 >= node大小,說明目標位置不在本node內,分別計算node偏移量和_M_curr偏移量,並更新iterator狀態
_Self& operator+=(difference_type __n)
  {
    difference_type __offset = __n + (_M_cur - _M_first);
    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))
      _M_cur += __n;
    else {
      difference_type __node_offset =
        __offset > 0 ? __offset / difference_type(_S_buffer_size())
                   : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
      _M_set_node(_M_node + __node_offset);
      _M_cur = _M_first + 
        (__offset - __node_offset * difference_type(_S_buffer_size()));
    }
    return *this;
  }

4 deque的擴縮策略

4.1 push_front和push_back

pop_front在隊列頭部壓入值, pop_back在隊列尾部壓入值。push_front和push_back是對稱操做,區別只在於方向不一樣。流程以下性能

deque執行push_front或者push_back的過程

執行時分爲如下幾種狀況:

  1. 頭部/尾部node中是否有可用空間,若是是,則直接用於容納新值,不然走2.
  2. deque map頭部/尾部是否有可用空間,若是是,則建立新node容納新值,不然走3.
  3. deque map中剩餘空間是否超過一半,若是是,則右移/左移全部node, 給deque map頭部/尾部騰出空間,而後走2.; 不然走4.
  4. 從新申請更大的deque map空間,複製舊map上的node到新map, 繼續走2.
void push_back(const value_type& __t) {
    if (_M_finish._M_cur != _M_finish._M_last - 1) {
      construct(_M_finish._M_cur, __t);
      ++_M_finish._M_cur;
    }
    else
      _M_push_back_aux(__t);
  }
  
template <class _Tp, class _Alloc>
void deque<_Tp,_Alloc>::_M_push_back_aux()
{
  _M_reserve_map_at_back();
  *(_M_finish._M_node + 1) = _M_allocate_node();
  __STL_TRY {
    construct(_M_finish._M_cur);
    _M_finish._M_set_node(_M_finish._M_node + 1);
    _M_finish._M_cur = _M_finish._M_first;
  }
  __STL_UNWIND(_M_deallocate_node(*(_M_finish._M_node + 1)));
}

4.2 pop_front和pop_back

pop_front在隊列頭部彈出值, pop_back在隊列尾部彈出值。
分兩種狀況:

  • 更新_M_start或者_M_finish以後仍在原node內,析構對象便可。
  • 更新_M_start或者_M_finish以後不在原node內,不只要析構對象,還要釋放node
void pop_back() {
    if (_M_finish._M_cur != _M_finish._M_first) {
      --_M_finish._M_cur;
      destroy(_M_finish._M_cur);
    }
    else
      _M_pop_back_aux();
  }

// Called only if _M_finish._M_cur == _M_finish._M_first.
template <class _Tp, class _Alloc>
void deque<_Tp,_Alloc>::_M_pop_back_aux()
{
  _M_deallocate_node(_M_finish._M_first);
  _M_finish._M_set_node(_M_finish._M_node - 1);
  _M_finish._M_cur = _M_finish._M_last - 1;
  destroy(_M_finish._M_cur);

4.3 insert

分三種狀況:

  • 插入位置在頭部,等效於push_front
  • 插入位置在尾部,等效於push_back
  • 插入位置既非頭部,又非尾部,判斷插入位置在前半部分仍是後半部分:
    • 前半部分: 執行push_front(front()), deque map前半部分左移,插入位置上構造對象。
    • 後半部分:執行push_back(back()), deque map後半部分右移,插入位置上構造對象
iterator insert(iterator position, const value_type& __x) {
    if (position._M_cur == _M_start._M_cur) {
      push_front(__x);
      return _M_start;
    }
    else if (position._M_cur == _M_finish._M_cur) {
      push_back(__x);
      iterator __tmp = _M_finish;
      --__tmp;
      return __tmp;
    }
    else {
      return _M_insert_aux(position, __x);
    }
  }

4.4 erase

判斷擦除位置pos在前半部分仍是後半部分

  • 前半部分:pos以前部分右移,而後執行pop_front
  • 後半部分:pos以後部分左移,而後執行pop_back
iterator erase(iterator __pos) {
    iterator __next = __pos;
    ++__next;
    difference_type __index = __pos - _M_start;
    if (size_type(__index) < (this->size() >> 1)) {
      copy_backward(_M_start, __pos, __next);
      pop_front();
    }
    else {
      copy(__next, _M_finish, __pos);
      pop_back();
    }
    return _M_start + __index;
  }

5 deque的適用場景

  1. 雙端隊列適合從兩端增長和刪除數據。不過在極端條件下,會產生節點申請和釋放,以及deque map的複製。
  2. 對隨機讀寫的支持較好,可是效率上不如vector, 由於索引到值的映射須要基於deque map進行計算。
  3. 對中間插入和刪除不友好,由於可形成屢次_Tp對象的copy操做。由於inserterase使用了push(pop)_front(back)``, 所以極端條件下,也會出現1.`中的狀況。

6 queue/stack/queue的關係

queuestack中缺省底層數據結構是deque

template <class _Tp, 
          class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(deque<_Tp>) >
class queue;

template <class _Tp, 
          class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(deque<_Tp>) >
class stack;

固然你也能夠本身實現支持front() back() push_front() pop_front() push_back() pop_back()等接口的_Sequence容器,不過建議仍是用默認設置,除非能保證你實現的_Sequence在性能可以超過deque

queue等效於隱藏了接口push_frontpop_backdeque, 而stack等效於隱藏了接口push_frontpop_frontdeque。 僅此而已,因此要研究queuestack的實現,最關鍵的仍是弄清楚deque

推薦閱讀

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

相關文章
相關標籤/搜索