1. 程式人生 > >STL原始碼分析之deque有序容器 中

STL原始碼分析之deque有序容器 中

前言

前一節我們分析了deque的基本使用, 本節我們來分析一下deque的對map的操作, 即插入, 刪除等. 但是本節只分析push, pop和刪除操作, 而insert操作有點複雜還是放到下節來分析.

push, pop

因為deque的是能夠雙向操作, 所以其push和pop操作都類似於list都可以直接有對應的操作. 需要注意的是list是連結串列, 並不會涉及到界線的判斷, 而deque是由陣列來儲存的, 就需要隨時對界線進行判斷.

push實現.

template <class T, class Alloc = alloc, size_t BufSiz =
0> class deque { ... public: // push_* and pop_* // 對尾進行插入 // 判斷函式是否達到了陣列尾部. 沒有達到就直接進行插入 void push_back(const value_type& t) { if (finish.cur != finish.last - 1) { construct(finish.cur, t); ++finish.cur; } else push_back_aux(t); }
// 對頭進行插入 // 判斷函式是否達到了陣列頭部. 沒有達到就直接進行插入 void push_front(const value_type& t) { if (start.cur != start.first) { construct(start.cur - 1, t); --start.cur; } else push_front_aux(t); } ... };

如果判斷陣列越界, 就移動到另一個數組進行push操作.

注意 : push_back是先執行構造在移動node, 而push_front

是先移動node在進行構造. 實現的差異主要是finish是指向最後一個元素的後一個地址而first指向的就只第一個元素的地址. 下面pop也是一樣的.

// Called only if finish.cur == finish.last - 1.
// 到達了陣列的尾部
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_back_aux(const value_type& t) {
  value_type t_copy = t;
  reserve_map_at_back();
  // 申請空間
  *(finish.node + 1) = allocate_node();
  __STL_TRY {
  	// 執行構造
    construct(finish.cur, t_copy);
    // 移動node, 指向下一個陣列的頭
    finish.set_node(finish.node + 1);
    finish.cur = finish.first;	// cur只指向當前陣列的頭
  }
  // 如果分配失敗, 釋放掉該記憶體
  __STL_UNWIND(deallocate_node(*(finish.node + 1)));
}

// Called only if start.cur == start.first.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t) {
  value_type t_copy = t;
  reserve_map_at_front();
  // 申請空間
  *(start.node - 1) = allocate_node();
  __STL_TRY {
  	// 先要移動node, 讓其指向上一個陣列的尾部
    start.set_node(start.node - 1);
    // cur指向當前陣列的尾部
    start.cur = start.last - 1;
    // 執行構造
    construct(start.cur, t_copy);
  }
#     ifdef __STL_USE_EXCEPTIONS
  catch(...) {
    start.set_node(start.node + 1);
    start.cur = start.first;
    deallocate_node(*(start.node - 1));
    throw;
  }
#     endif /* __STL_USE_EXCEPTIONS */
} 

pop實現.

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
    ...
public: 
    // 對尾部進行操作
    // 判斷是否達到陣列的頭部. 沒有到達就直接釋放
    void pop_back() {
    if (finish.cur != finish.first) {
      --finish.cur;
      destroy(finish.cur);
    }
    else
      pop_back_aux();
  }
    // 對頭部進行操作
    // 判斷是否達到陣列的尾部. 沒有到達就直接釋放
  void pop_front() {
    if (start.cur != start.last - 1) {
      destroy(start.cur);
      ++start.cur;
    }
    else 
      pop_front_aux();
  }
    ...
};

pop判斷越界後執行以下函式.

// Called only if finish.cur == finish.first.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>:: pop_back_aux() {
  deallocate_node(finish.first);	// 先呼叫解構函式
  finish.set_node(finish.node - 1);	// 再移動node
  finish.cur = finish.last - 1;		// 然後cur指向當前陣列的最後位置
  destroy(finish.cur);				// 最後釋放記憶體空間.
}

// Called only if start.cur == start.last - 1.  Note that if the deque
//  has at least one element (a necessary precondition for this member
//  function), and if start.cur == start.last, then the deque must have
//  at least two nodes.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::pop_front_aux() {
  destroy(start.cur);				// 先釋放記憶體空間.
  deallocate_node(start.first);		// 再呼叫解構函式
  start.set_node(start.node + 1);	// 然後移動node
  start.cur = start.first;			// 最後cur指向當前陣列的第一個位置
}   	

reserve_map_at一類函式. pop和push都先呼叫了reserve_map_at_XX函式, 這些函式主要是為了判斷前後空間是否足夠.

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
    ...
  public:
  void new_elements_at_front(size_type new_elements);
  void new_elements_at_back(size_type new_elements);

  void destroy_nodes_at_front(iterator before_start);
  void destroy_nodes_at_back(iterator after_finish);

protected:                      // Allocation of map and nodes
  // Makes sure the map has space for new nodes.  Does not actually
  //  add the nodes.  Can invalidate map pointers.  (And consequently, 
  //  deque iterators.)
	// 始終保證後面要有一個及以上的空陣列大小
  void reserve_map_at_back (size_type nodes_to_add = 1) {
    if (nodes_to_add + 1 > map_size - (finish.node - map))
      reallocate_map(nodes_to_add, false);
  }
	// 始終保證前面要有一個及以上的空陣列大小
  void reserve_map_at_front (size_type nodes_to_add = 1) {
    if (nodes_to_add > start.node - map)
      reallocate_map(nodes_to_add, true);
  }

  void reallocate_map(size_type nodes_to_add, bool add_at_front);
    ...
};

reallocate_map函式, 空間不足

  1. deque空間實際足夠
    1. deque內部進行調整start, 和finish
  2. deque空間真的不足
    1. 申請更大的空間
    2. 拷貝元素過去
    3. 修改map和start, finish指向
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add, bool add_at_front) 
{
    // 儲存現在的空間大小和新的空間大小
  size_type old_num_nodes = finish.node - start.node + 1;
  size_type new_num_nodes = old_num_nodes + nodes_to_add;

  map_pointer new_nstart;
    // map_size > 2 * new_num_nodes 發現deque空間還很充足就只是調整deque內部的元素就行了, 沒必要重新開空間
    // 這種情況主要出現在一直往首或尾單方向插入元素, 導致首(尾)前面還有很多餘留的空間, 這種情況就這樣調整
  if (map_size > 2 * new_num_nodes) {
    new_nstart = map + (map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
    if (new_nstart < start.node)
      copy(start.node, finish.node + 1, new_nstart);
    else
      copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
  }
    // 空間是真的不夠了
  else {
    size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;
	// 分配空間. 重新定位start的位置
    map_pointer new_map = map_allocator::allocate(new_map_size);
    new_nstart = new_map + (new_map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
      // 拷貝原deque元素, 最後釋放掉原記憶體空間
    copy(start.node, finish.node + 1, new_nstart);
    map_allocator::deallocate(map, map_size);
	
      // 調整map
    map = new_map;
    map_size = new_map_size;
  }
	// 重新調整start, finish
  start.set_node(new_nstart);
  finish.set_node(new_nstart + old_num_nodes - 1);
}
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::destroy_nodes_at_front(iterator before_start) {
  for (map_pointer n = before_start.node; n < start.node; ++n)
    deallocate_node(*n);
}

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::destroy_nodes_at_back(iterator after_finish) {
  for (map_pointer n = after_finish.node; n > finish.node; --n)
    deallocate_node(*n);
}
template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
    ...
public:  
  iterator reserve_elements_at_front(size_type n) {
    size_type vacancies = start.cur - start.first;
    if (n > vacancies) 
      new_elements_at_front(n - vacancies);
    return start - difference_type(n);
  }

  iterator reserve_elements_at_back(size_type n) {
    size_type vacancies = (finish.last - finish.cur) - 1;
    if (n > vacancies)
      new_elements_at_back(n - vacancies);
    return finish + difference_type(n);
  }
    ...
};
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::new_elements_at_front(size_type new_elements) {
  size_type new_nodes = (new_elements + buffer_size() - 1) / buffer_size();
  reserve_map_at_front(new_nodes);
  size_type i;
  __STL_TRY {
    for (i = 1; i <= new_nodes; ++i)
      *(start.node - i) = allocate_node();
  }
#       ifdef __STL_USE_EXCEPTIONS
  catch(...) {
    for (size_type j = 1; j < i; ++j)
      deallocate_node(*(start.node - j));      
    throw;
  }
#       endif /* __STL_USE_EXCEPTIONS */
}

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::new_elements_at_back(size_type new_elements) {
  size_type new_nodes = (new_elements + buffer_size() - 1) / buffer_size();
  reserve_map_at_back(new_nodes);
  size_type i;
  __STL_TRY {
    for (i = 1; i <= new_nodes; ++i)
      *(finish.node + i) = allocate_node();
  }
#       ifdef __STL_USE_EXCEPTIONS
  catch(...) {
    for (size_type j = 1; j < i; ++j)
      deallocate_node(*(finish.node + j));      
    throw;
  }
#       endif /* __STL_USE_EXCEPTIONS */
}

刪除操作

不知道還記得我們最開始建構函式呼叫create_map_and_nodes考慮到deque實現前後插入時間複雜度為O(1), 保證了在前後留出了空間, 所以push和pop都可以在前面的陣列進行操作.

好了, 現在就來看erase. 因為deque的是由陣列構成, 所以地址空間是連續的. 刪除也就像vector一樣, 要移動所有的元素, deque為了保證效率儘量高, 就判斷刪除的位置是中間偏後還是中間偏前來進行移動.

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
    ...
public:                         // Erase
  iterator erase(iterator pos) 
  {
    iterator next = pos;
    ++next;
    difference_type index = pos - start;
      // 刪除的地方是中間偏前, 移動前面的元素
    if (index < (size() >> 1)) 
    {
      copy_backward(start, pos, next);
      pop_front();
    }
      // 刪除的地方是中間偏後, 移動後面的元素
    else {
      copy(next, finish, pos);
      pop_back();
    }
    return start + index;
  }
	// 範圍刪除, 實際也是呼叫上面的erase函式.
  iterator erase(iterator first, iterator last);
  void clear(); 
    ...
};

erase(iterator first, iterator last)


template <class T, class Alloc, size_t BufSize>
deque<T, Alloc, BufSize>::iterator 
deque<T, Alloc, BufSize>::erase(iterator first, iterator last) 
{
  if (first == start && last == finish) {
    clear();
    return finish;
  }
  else {
      // 計算出兩個迭代器的距離, 畢竟是連續的, 可以直接計算
    difference_type n = last - first;
      // 同樣, 選擇前後哪種方法移動.
    difference_type elems_before = first - start;
      // 刪除的地方是中間偏前, 移動前面的元素
    if (elems_before < (size() - n) / 2) {
      copy_backward(start, first, last);
      iterator new_start = start + n;
      destroy(start, new_start);
        // 可能會涉及到跨陣列的問題(使用者使用並不知道)
      for (map_pointer cur = start.node; cur < new_start.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      start = new_start;
    }
      // 刪除的地方是中間偏後, 移動後面的元素
    else {
      copy(last, finish, first);
      iterator new_finish = finish - n;
      destroy(new_finish, finish);
        // 可能會涉及到跨陣列的問題(使用者使用並不知道)
      for (map_pointer cur = new_finish.node + 1; cur <= finish.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      finish = new_finish;
    }
    return start + elems_before;
  }
}

clear函式. 刪除所有元素. 分兩步執行:

  1. 從第二個陣列開始到倒數第二個陣列一次性全部刪除, 畢竟中間的陣列肯定都是滿的, 前後兩個陣列就不一定是填充滿的.
  2. 刪除前後兩個陣列的元素
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::clear() {
	// 從第二個陣列開始到倒數第二個陣列一次性全部刪除
	// 畢竟中間的陣列肯定都是滿的, 前後兩個陣列就不一定是填充滿的.
  for (map_pointer node = start.node + 1; node < finish.node; ++node) {
    destroy(*node, *node + buffer_size());
    data_allocator::deallocate(*node, buffer_size());
  }
	// 刪除前後兩個陣列的元素.
  if (start.node != finish.node) {
    destroy(start.cur, start.last);
    destroy(finish.first, finish.cur);
    data_allocator::deallocate(finish.first, buffer_size());
  }
  else
    destroy(start.cur, finish.cur);

  finish = start;
}

swap

deque的swap操作也只是交換了start, finish, map, 並沒有交換所有的元素.

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
        ...
    	void swap(deque& x)
        {
        	__STD::swap(start, x.start);
        	__STD::swap(finish, x