STL略观——deque 的构造和内存管理constructor()、push_back() 和 push_front()

  STL 所有容器应用到了空间配置器,当然 deque 在 _Deque_base 中设置了 两个空间配置器,一个负责缓冲区元素的空间配置,一个负责中控器map的指针空间配置:

  typedef simple_alloc<_Tp, _Alloc>  _Node_alloc_type;    //负责缓冲区元素空间配置
  typedef simple_alloc<_Tp*, _Alloc> _Map_alloc_type;     //负责中控器map的指针空间配置

  当然可以追溯下元素空间配置用的配置器是啥:

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

  咱们看看 _Base::get_allocator() 在哪里

  typedef _Alloc allocator_type;
  allocator_type get_allocator() const { return allocator_type(); }

  可以知道,其实 _Base::get_allocator() 还是默认的空间配置器 _Alloc;

  deque 提供的 constructor 如下:

  //拷贝构造函数
 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); }

  _Base(__a,__n)负责产生并安排好 deque 的结构,看一下_M_fill_initialized 在哪里,如下,该函数将元素的初值设定妥当:

template <class _Tp, class _Alloc>
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))); }

  也看一下_Base(__a,__n)如何设定deque结构,根据 typedef  _Deque_base<_Tp, _Alloc>  _Base,找到_Deque_base构造函数如下:

    _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_map等,这里就不多说了,找一下_M_initialize_map 函数

template <class _Tp, class _Alloc>
void
_Deque_base<_Tp,_Alloc>::_M_initialize_map(size_t __num_elements)
{
    //节点数 = 元素总的个数 / 缓冲区大小 再 + 1;
    size_t __num_nodes = 
    __num_elements / __deque_buf_size(sizeof(_Tp)) + 1;
    //中控器大小,最少为初始设定个数8个,最多“所需节点数 + 2”
    _M_map_size = max((size_t) _S_initial_map_size, __num_nodes + 2);
    //配置具有_M_map_size个节点的map中控器
    _M_map = _M_allocate_map(_M_map_size);
    //设置头部和尾部
    _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内的两个迭代_M_start 和 _M_finish更新内容
    _M_start._M_set_node(__nstart);
    _M_finish._M_set_node(__nfinish - 1);
    _M_start._M_cur = _M_start._M_first;
    //多配置一个节点,_M_cur指向多配置的节点起始处
    _M_finish._M_cur = _M_finish._M_first +
               __num_elements % __deque_buf_size(sizeof(_Tp));
}

   接下来是push_back()函数,push_back()函数首先判断在缓冲区是否有两个以上的元素备用空间,如果有则直接构造,没有就调用push_back_aux()函数,先配置一块新的缓冲区,然后设置新元素内容,然后更改迭代器 finish 状态:

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
        //没有就调用该函数进行配置新的空间,并设置finish状态
        _M_push_back_aux(__t);
}

  咱们再来看一下_M_push_back() 和 _M_push_front()函数,_M_push_back()函数是当map尾部没有多余节点存储指向新的缓冲区的新指针的时候,需要额外在尾部继续开辟一个新的空间,来存放新的指针,同理,_M_push_front()函数在头部开辟新的空间来存储指针,当然要满足空间不足的前提。

  //当map尾部还剩下一个节点(节点存取指向缓冲区的指针)时,就必须重新换一个map,配置更大的,拷贝原来的,释放原来的
void _M_reserve_map_at_back (size_type __nodes_to_add = 1)
  {
    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);
  }

  特别的,当map很大,需要开辟额外的新的一块内存用来迁移map时,就会调用_M_reallocate_map来开辟新的内存,如下:

template <class _Tp, class _Alloc>
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;    //旧的map的size
    size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;               //新的map的size

    _Map_pointer __new_nstart;
    if (_M_map_size > 2 * __new_num_nodes)                                     //map目前的size如果大于两倍的新的size
    {
        __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
    {
        size_type __new_map_size = 
          _M_map_size + max(_M_map_size, __nodes_to_add) + 2;
     //配置一块空间,给map用
        _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);
        copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
        _M_deallocate_map(_M_map, _M_map_size);
     //设定新map起始地点大小
        _M_map = __new_map;
        _M_map_size = __new_map_size;
    }
  //重新设置开始迭代器和结束迭代器 _M_start._M_set_node(__new_nstart); _M_finish._M_set_node(__new_nstart
+ __old_num_nodes - 1); }
既然选择了远方,便只顾风雨兼程
原文地址:https://www.cnblogs.com/Forever-Road/p/6838203.html