STL容器之deque源码详解

  • 简介
  • 构造函数
  • 主要函数
    • deque(size_type, const value_type&, const allocator_type&)
    • push_back(const value_type&)
    • push_front(const value_type&)
    • pop_back()
    • pop_front()
    • clear()
  • 特点
    • 与vector的区别
  • 参考资料

简介

deque是一种双向开口的连续性空间,可以在头尾两端分别做元素的插入删除操作,deque没有所谓的容量(capacity)概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新空间并链接起来。

STL容器之deque源码详解_第1张图片

虽然deque也提供了Random access iterator,但是它的迭代器不是普通指针,其复杂程度很高,同时,对于deque的排序操作,为了高效率,可以先将deque复制到vector中,排序之后,再复制回deque。

构造函数

// 默认构造函数
explicit deque(const allocator_type& __a = allocator_type()) 
: _Base(__a, 0) {}
// 拷贝构造函数
deque(const deque& __x) : _Base(__x.get_allocator(), __x.size()) 
{ uninitialized_copy(__x.begin(), __x.end(), _M_start); }
// 指定初始元素数目
deque(size_type __n, const value_type& __value,
    const allocator_type& __a = allocator_type()) : _Base(__a, __n)
{ _M_fill_initialize(__value); }
// 为每一个节点的缓冲区设定初值
deque(const value_type* __first, const value_type* __last,
    const allocator_type& __a = allocator_type()) 
: _Base(__a, __last - __first)
{ uninitialized_copy(__first, __last, _M_start); }
deque(const_iterator __first, const_iterator __last,
    const allocator_type& __a = allocator_type()) 
: _Base(__a, __last - __first)
{ uninitialized_copy(__first, __last, _M_start); }

deque的迭代器数据结构为:

template 
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 random_access_iterator_tag iterator_category;  // Random access iterator
  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  typedef _Tp** _Map_pointer;

  typedef _Deque_iterator _Self;

  _Tp* _M_cur;   // 迭代器指向缓冲区的当前元素
  _Tp* _M_first; // 迭代器指向缓冲区的头部
  _Tp* _M_last;  // 迭代器指向缓冲区的尾部
  _Map_pointer _M_node;  // 迭代器指向map控制中心
}

deque主要的数据结构有:

template 
class deque : protected _Deque_base<_Tp, _Alloc> {
  typedef _Deque_base<_Tp, _Alloc> _Base;
public:                         // Basic types
  typedef _Tp value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  typedef typename _Base::allocator_type allocator_type;
  allocator_type get_allocator() const { return _Base::get_allocator(); }

public:                         // Iterators
  typedef typename _Base::iterator       iterator;
  typedef typename _Base::const_iterator const_iterator;
  typedef pointer* _Map_pointer;

protected:
  _Tp** _M_map;         // 指向map,map是一块连续空间,其中每个元素都是指针,指向一块缓冲区
  size_t _M_map_size;   // map可以容纳的指针数目
  iterator _M_start;    // 头部迭代器
  iterator _M_finish;   // 尾部迭代器
}

deque是采用一块所谓的map作为主控,这里的map是一块连续空间,其中每个元素都是指针,指向一块缓冲区,如下图所示:
STL容器之deque源码详解_第2张图片

主要函数

我们首先以deque的一个构造函数来进行讲解

deque(size_type, const value_type&, const allocator_type&)

该构造函数原型为

deque(size_type __n, const value_type& __value,
    const allocator_type& __a = allocator_type()) : _Base(__a, __n)
{ _M_fill_initialize(__value); }

该构造函数指定了deque的初始元素数量以及初始值,其内部会先调用基类的初始化操作,然后再通过_M_fill_initialize函数来进行数据填充,基类的构造函数为

_Deque_base(const allocator_type&, size_t __num_elements)
    : _M_map(0), _M_map_size(0),  _M_start(), _M_finish() {
    _M_initialize_map(__num_elements);
}

该构造函数通过_M_initialize_map函数来对map主控器进行初始化,其初始化过程如下:

template 
void
_Deque_base<_Tp,_Alloc>::_M_initialize_map(size_t __num_elements)
{
  // map所需节点数 = (元素个数/每个缓冲区可容纳的元素个数) + 1
  // 如果刚好整除,则会多分配一个节点
  size_t __num_nodes = 
    __num_elements / __deque_buf_size(sizeof(_Tp)) + 1;
  // 一个 map 要管理几个节点,最少 8 个,最多是所需节点数加 2,前后各留一个备用,扩充时可用
  _M_map_size = max((size_t) _S_initial_map_size, __num_nodes + 2);
  _M_map = _M_allocate_map(_M_map_size);

  // __nstart、__nfinish 指向 map 的中间位置
  // 保持头尾两端的扩充容量一样大
  _Tp** __nstart = _M_map + (_M_map_size - __num_nodes) / 2;
  _Tp** __nfinish = __nstart + __num_nodes;
    
  __STL_TRY {
    // 为map内的每个现用节点配置缓冲区
    _M_create_nodes(__nstart, __nfinish);
  }
  __STL_UNWIND((_M_deallocate_map(_M_map, _M_map_size), 
                _M_map = 0, _M_map_size = 0));
  // 为 deque 内的两个迭代器 start 和 finish 指向正确位置
  _M_start._M_set_node(__nstart);
  _M_finish._M_set_node(__nfinish - 1);
  _M_start._M_cur = _M_start._M_first;
  _M_finish._M_cur = _M_finish._M_first +
               __num_elements % __deque_buf_size(sizeof(_Tp));
}

_M_fill_initialize源码如下:

template 
void deque<_Tp,_Alloc>::_M_fill_initialize(const value_type& __value) {
  _Map_pointer __cur;
  __STL_TRY {
    // 为每个节点的缓冲区设置初始值
    for (__cur = _M_start._M_node; __cur < _M_finish._M_node; ++__cur)
      uninitialized_fill(*__cur, *__cur + _S_buffer_size(), __value);
      // 最后一个节点有点不同,因为尾端可能会有备用空间,不必设置初值
      uninitialized_fill(_M_finish._M_first, _M_finish._M_cur, __value);
  }
  __STL_UNWIND(destroy(_M_start, iterator(*__cur, __cur)));
}

下面来看添加元素时的代码。

push_back(const value_type&)

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);
}

_M_push_back_aux代码如下:

template 
void deque<_Tp,_Alloc>::_M_push_back_aux()
{
  // 若符合某种条件,则必须重换一个map
  _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);        // 改变finish,令其指向新节点
    _M_finish._M_cur = _M_finish._M_first;                        // 设定finish的新状态
  }
  __STL_UNWIND(_M_deallocate_node(*(_M_finish._M_node + 1)));
}

push_back是向尾部添加元素,下面来看deque先首部添加元素的函数push_front。

push_front(const value_type&)

void push_front(const value_type& __t) {
    // 第一缓冲区尚有备用空间
    if (_M_start._M_cur != _M_start._M_first) {
      // 直接在备用空间上构造元素,并更新使用状态
      construct(_M_start._M_cur - 1, __t);
      --_M_start._M_cur;
    }
    else   // 第一缓冲区已无备用空间
      _M_push_front_aux(__t);
}

_M_push_front_aux源码为

template 
void deque<_Tp,_Alloc>::_M_push_front_aux()
{
  // 若符合某种条件,则必须重换一个map
  _M_reserve_map_at_front();
  // 配置新节点
  *(_M_start._M_node - 1) = _M_allocate_node();
  __STL_TRY {
    _M_start._M_set_node(_M_start._M_node - 1);    // 改变start,令其指向新节点
    _M_start._M_cur = _M_start._M_last - 1;               // 设定start的初始值
    construct(_M_start._M_cur);                                   // 针对标的元素设值
  }
  __STL_UNWIND((++_M_start, _M_deallocate_node(*(_M_start._M_node - 1))));
}

在添加元素时,都会涉及到重换map的操作,那何时重换map呢,其源代码如下:

void _M_reserve_map_at_back (size_type __nodes_to_add = 1) {
    // 如果map尾端的节点备用空间不足,则重换map,即配置更大的map,拷贝原来的map,并释放原来的map
    if (__nodes_to_add + 1 > _M_map_size - (_M_finish._M_node - _M_map))
        _M_reallocate_map(__nodes_to_add, false);
}

void _M_reserve_map_at_front (size_type __nodes_to_add = 1) {
    if (__nodes_to_add > size_type(_M_start._M_node - _M_map))
        _M_reallocate_map(__nodes_to_add, true);
}

最终操作都是由_M_reallocate_map来完成的,其源码如下

// 重新分配 map 连续空间
template 
void deque<_Tp,_Alloc>::_M_reallocate_map(size_type __nodes_to_add,
                                          bool __add_at_front)
{
  size_type __old_num_nodes = _M_finish._M_node - _M_start._M_node + 1;
  size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;

  _Map_pointer __new_nstart;
  if (_M_map_size > 2 * __new_num_nodes) {
    __new_nstart = _M_map + (_M_map_size - __new_num_nodes) / 2 
                     + (__add_at_front ? __nodes_to_add : 0);
    if (__new_nstart < _M_start._M_node)
      copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
    else
      copy_backward(_M_start._M_node, _M_finish._M_node + 1, 
                    __new_nstart + __old_num_nodes);
  }
  else {
    // 配置一块新空间,准备给新map使用
    size_type __new_map_size = 
      _M_map_size + max(_M_map_size, __nodes_to_add) + 2;

    _Map_pointer __new_map = _M_allocate_map(__new_map_size);
    __new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2
                         + (__add_at_front ? __nodes_to_add : 0);
    // 把原来map的内容拷贝过来
    copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
    // 释放掉原来的map
    _M_deallocate_map(_M_map, _M_map_size);

    _M_map = __new_map;
    _M_map_size = __new_map_size;
  }
  // 重新设置迭代器start和finish
  _M_start._M_set_node(__new_nstart);
  _M_finish._M_set_node(__new_nstart + __old_num_nodes - 1);
}

下面来看取出元素的函数操作。

pop_back()

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();
}

_M_pop_back_aux源码为:

template 
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);     // 调整finish的状态,使其指向前一个缓冲区的最后一个元素
  _M_finish._M_cur = _M_finish._M_last - 1;
  destroy(_M_finish._M_cur);                                        // 将该元素析构
}

pop_front()

void pop_front() {
    // 第一个缓冲区有两个或者更多元素
    if (_M_start._M_cur != _M_start._M_last - 1) {
      destroy(_M_start._M_cur);
      ++_M_start._M_cur;
    }
    else   // 第一缓冲区仅有一个元素
      _M_pop_front_aux();
}

_M_pop_front_aux源码为

template 
void deque<_Tp,_Alloc>::_M_pop_front_aux()
{
  destroy(_M_start._M_cur);                                        // 将第一个缓冲区的第一个元素析构
  _M_deallocate_node(_M_start._M_first);                  // 释放第一缓冲区
  _M_start._M_set_node(_M_start._M_node + 1);      // 调整start的状态,使其指向后一个缓冲区的第一个元素
  _M_start._M_cur = _M_start._M_first;
}      

clear()

template  
void deque<_Tp,_Alloc>::clear()
{
  // 对于头尾以外的所有缓冲区
  for (_Map_pointer __node = _M_start._M_node + 1;
       __node < _M_finish._M_node;
       ++__node) {
    // 将缓冲区内的元素析构
    destroy(*__node, *__node + _S_buffer_size());
    // 释放缓冲区去内存
    _M_deallocate_node(*__node);
  }
  // 至少有头尾两个缓冲区
  if (_M_start._M_node != _M_finish._M_node) {
    // 将头缓冲区的元素析构
    destroy(_M_start._M_cur, _M_start._M_last);
    // 将尾缓冲区的元素析构
    destroy(_M_finish._M_first, _M_finish._M_cur);
    // 释放尾缓冲区内存,保留头缓冲区
    _M_deallocate_node(_M_finish._M_first);
  }
  else    // 只有一个缓冲区
    // 将该缓冲区的元素析构,但保留缓冲区空间
    destroy(_M_start._M_cur, _M_finish._M_cur);
  _M_finish = _M_start;
}

特点

deque是一种双向开口的连续性空间,可以在头尾两端分别做元素的插入删除操作,deque使用连续的map中控来管理许多不连续的元素缓冲区,虽然deque提供了Random access iterator,但是由于它的特殊结构,导致迭代器的复杂程度较高,源码就不再一一介绍。

与vector的区别

  • deque两端都能够快速插入和删除元素,vector只能在尾端进行。
  • deque的元素存取和迭代器操作会稍微慢一些,因为deque的内部结构会多一个间接过程。
  • 迭代器是特殊的智能指针,而不是一般指针,它需要在不同的区块之间跳转。
  • 因为deque使用不止一块内存(而vector必须使用一块连续内存),所以deque的max_size()可能更大。

参考资料

侯捷 《STL源码剖析》
SGI-STL V3.3 源代码的学习

你可能感兴趣的:(c++)