1. 程式人生 > >SGISTL原始碼閱讀十五 deque容器中

SGISTL原始碼閱讀十五 deque容器中

SGISTL原始碼閱讀十五 deque容器中

前言

我們已經學習了deque的資料結構和它的迭代器,接下來我們繼續學習它的構造及記憶體申請等內容。


深入原始碼

deque的建構函式

  • 預設建構函式
/* 指標陣列不申請空間
 * 並且first和finish迭代器也呼叫預設建構函式
 */
public:
  deque()
    : start(), finish(), map(0), map_size(0)
  {
    create_map_and_nodes(0);
  }
  • 拷貝建構函式
  /* 分配了空間之後
   * 將x中的元素拷貝過去
   */
  deque(const deque& x)
    : start(), finish(), map(0), map_size(0)
  {
    //申請指標陣列map的大小及線性空間(緩衝區buffer)
    create_map_and_nodes(x.size());
    __STL_TRY {
      uninitialized_copy(x.begin(), x.end(), start);
    }
    __STL_UNWIND(destroy_map_and_nodes());
  }
  • 初始化n個值為value的元素
    根據不同的情況設計了不同的過載版本,它們都呼叫了fill_initialize函式
  deque(size_type n, const value_type& value)
    : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value);
  }
  deque(int n, const value_type& value)
    : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value);
  }
  deque(long n, const value_type& value)
    : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value);
  }
  • 構造n個值為預設值的元素
    這裡的explicit關鍵字已經見到過幾次了,用途是防止隱式轉換
  explicit deque(size_type n)
    : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value_type());
  }
  • 迭代器範圍構造
    同樣根據不同的情況有多個不同的過載版本
#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  deque(InputIterator first, InputIterator last)
    : start(), finish(), map(0), map_size(0)
  {
    //range_initialize根據迭代器的型別選擇最優的初始化方式
    range_initialize(first, last, iterator_category(first));
  }
#else /* __STL_MEMBER_TEMPLATES */
  deque(const value_type* first, const value_type* last)
    : start(), finish(), map(0), map_size(0)
  {
    create_map_and_nodes(last - first);
    __STL_TRY {
      uninitialized_copy(first, last, start);
    }
    __STL_UNWIND(destroy_map_and_nodes());
  }
  deque(const_iterator first, const_iterator last)
    : start(), finish(), map(0), map_size(0)
  {
    create_map_and_nodes(last - first);
    __STL_TRY {
      uninitialized_copy(first, last, start);
    }
    __STL_UNWIND(destroy_map_and_nodes());
  }
#endif /* __STL_MEMBER_TEMPLATES */
create_map_and_nodes

申請指標陣列map的大小及線性空間(緩衝區buffer)

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
  /* 計算結點個數
   * buffer_size()返回每段線性空間能容量元素的個數
   * num_elements代表需要申請的元素的個數
   * num_nodes代表需要申請的結點個數
   */
  size_type num_nodes = num_elements / buffer_size() + 1;
  /* map至少有8個結點,最多num_nodes + 2
   * 之所以要設計成num_nodes + 2
   * 是為了當插入元素分別到尾部和首部時有一定的擴充套件性,而不用一插入元素就分配空間
   */
  map_size = max(initial_map_size(), num_nodes + 2);
  //申請空間
  map = map_allocator::allocate(map_size);
  map_pointer nstart = map + (map_size - num_nodes) / 2;
  map_pointer nfinish = nstart + num_nodes - 1;
  map_pointer cur;
  __STL_TRY {
    /* 申請一整段線性空間
     * allocate_node的原型如下
     * return data_allocator::allocate(buffer_size();
     */
    for (cur = nstart; cur <= nfinish; ++cur)
      *cur = allocate_node();
  }
#     ifdef  __STL_USE_EXCEPTIONS
  catch(...) {
    //處理異常情況
    for (map_pointer n = nstart; n < cur; ++n)
      deallocate_node(*n);
    map_allocator::deallocate(map, map_size);
    throw;
  }
#     endif /* __STL_USE_EXCEPTIONS */
  //維護start和finish迭代器
  start.set_node(nstart);
  finish.set_node(nfinish);
  start.cur = start.first;
  finish.cur = finish.first + num_elements % buffer_size();
}

deque的初始化操作

fill_initialize
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::fill_initialize(size_type n,
                                               const value_type& value) {
  //建立map和nodes
  create_map_and_nodes(n);
  map_pointer cur;
  __STL_TRY {
    //初始化每一個node節點
    //初始化未初始化的值為value
    for (cur = start.node; cur < finish.node; ++cur)
      uninitialized_fill(*cur, *cur + buffer_size(), value);
    //隨最後一個節點特殊處理,因為最後一個節點不一定會被填滿
    uninitialized_fill(finish.first, finish.cur, value);
  }
#       ifdef __STL_USE_EXCEPTIONS
  catch(...) {
    //處理異常情況,將所有節點銷燬
    for (map_pointer n = start.node; n < cur; ++n)
      destroy(*n, *n + buffer_size());
    destroy_map_and_nodes();
    throw;
  }
#       endif /* __STL_USE_EXCEPTIONS */
}
range_initialize

在進行構造的時候傳入的迭代器也有可能是別的型別的迭代器,不一定是random_access_iterator
以下range_initialize根據不同的情況寫了不同的過載版本

template <class T, class Alloc, size_t BufSize>
template <class InputIterator>
void deque<T, Alloc, BufSize>::range_initialize(InputIterator first,
                                                InputIterator last,
                                                input_iterator_tag) {
  create_map_and_nodes(0);
  for ( ; first != last; ++first)
    //一個節點一個節點地插入
    push_back(*first);
}

template <class T, class Alloc, size_t BufSize>
template <class ForwardIterator>
void deque<T, Alloc, BufSize>::range_initialize(ForwardIterator first,
                                                ForwardIterator last,
                                                forward_iterator_tag) {
  size_type n = 0;
  //計算出[first, last)範圍內的節點個數
  distance(first, last, n);
  //申請空間
  create_map_and_nodes(n);
  __STL_TRY {
    //拷貝
    uninitialized_copy(first, last, start);
  }
  //失敗則直接銷燬
  __STL_UNWIND(destroy_map_and_nodes());
}

deque的析構和記憶體釋放

解構函式
  ~deque() {
    destroy(start, finish);
    destroy_map_and_nodes();
  }
destroy_map_and_nodes()
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::destroy_map_and_nodes() {
  for (map_pointer cur = start.node; cur <= finish.node; ++cur)
    deallocate_node(*cur);
  //釋放空間
  map_allocator::deallocate(map, map_size);
}

總結

我們學習了deque的構造,析構等。
我們可以看出deque為了維護它獨特的資料結構付出了很多代價,很多的操作都變得異常艱難和複雜,但是隻要理解到它的資料結構這些都不難理解。接下來我們將繼續學習deque的一些相關操作,要看懂這些程式碼的前提是我們必須深刻地理解deque的資料結構。