顺序容器deque
deque 是一种双向开口的连续线性空间,所谓双向开口,就是可以在头尾两端分别做元素的插入和删除;与vector 相比,vector可以在头尾两端进行操作,但是其头部的操作效率奇差,无法接受。
deque是分段连续线性空间,随时可以增加一段新的空间;与vector相比,vector当内存不够时,需重新分配、复制数据、释放原始空间。
deque的迭代器需要处理段之间的过渡,相比于vector的普通指针迭代器要复杂的多。
deque的类结构图
struct _Deque_impl
: public _Tp_alloc_type
{
_Tp** _M_map;
size_t _M_map_size;//指向的缓冲区数目
iterator _M_start;//用于管理中控器map
iterator _M_finish;//用于管理中控器map
_Deque_impl()
: _Tp_alloc_type(), _M_map(0), _M_map_size(0),
_M_start(), _M_finish()
{ }
_Deque_impl(const _Tp_alloc_type& __a) _GLIBCXX_NOEXCEPT
: _Tp_alloc_type(__a), _M_map(0), _M_map_size(0),
_M_start(), _M_finish()
{ }
#if __cplusplus >= 201103L
_Deque_impl(_Tp_alloc_type&& __a) _GLIBCXX_NOEXCEPT
: _Tp_alloc_type(std::move(__a)), _M_map(0), _M_map_size(0),
_M_start(), _M_finish()
{ }
#endif
};
deque的中控器
deque采用一块所谓的map作为主控,这里所谓map是一小块连接空间,其中每一个元素都是指针,指向另一段(较大的)连续线性空间,称为缓冲区,缓冲区才是deque的储存空间主体。
从上述的类结构图可以看出中控器的实现在_Deque_impl中,源码如下:
deque的结构设计中,map和buffer 之间的关系:
deque的迭代器
deque是分段连续空间,维持其“整体连续”假象的任务,落在了迭代器的operator++和 operator--两个运算子身上,它必须能够指出分段连续空间在哪里,其次必须能够判断自己是否已经处于其所在缓冲区的边缘,一旦前进或者后退就必须跳跃至下一个或上一个缓冲区;看下__deque_iterator源码:
template<typename _Tp, typename _Ref, typename _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() _GLIBCXX_NOEXCEPT
{ return __deque_buf_size(sizeof(_Tp)); }//获取缓冲区的大小typedef std::random_access_iterator_tag iterator_category;
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;//指向管控中心//。。。
}
deque的中控器、缓冲区、迭代器的相互关系如下:
再来看中控器的两个迭代器成员:
iterator _M_start;//用于管理中控器map
iterator _M_finish;//用于管理中控器map
中控器中的两个迭代器的表示形式:
结合deque的源码理解上述的start 和 finish两个迭代器:
iterator
begin() _GLIBCXX_NOEXCEPT
{ return this->_M_impl._M_start; }//这里的_M_start,就是中控器中的成员,对应图中的start
iterator
end() _GLIBCXX_NOEXCEPT
{ return this->_M_impl._M_finish; }//这里的_M_finish,就是中控器中的成员,对应图中的finish
迭代器 前++ 的实现
deque是分段连续线性空间,迭代器就要来维持其“整体连续”假象的任务,operator的具体实现如下:
_Self&
operator++() _GLIBCXX_NOEXCEPT
{
++_M_cur;//迭代器中的_M_cur为真正指向当前缓冲区的元素,加1 后移到下一个元素
if (_M_cur == _M_last)//判断当前的位置是否超出缓冲区范围,后开区间
{
_M_set_node(_M_node + 1);//操作指向的缓冲区,使其加1 指向下一个缓冲区
_M_cur = _M_first;//更新当前指向的实际元素
}
return *this;//返回迭代器
}
_M_set_node为操作中控器,来看下使如何操作缓冲区的:
void
_M_set_node(_Map_pointer __new_node) _GLIBCXX_NOEXCEPT
{
_M_node = __new_node;//使中控器指向新的缓冲区
_M_first = *__new_node;//_M_first 表示当前缓冲区的第一个元素位置
_M_last = _M_first + difference_type(_S_buffer_size());//_M_last 表示当前缓冲区的最后一个元素的下一个位置
}
_S_buffer_size实际调用下面的__deque_buf_size函数,用来获取每个缓冲的大小:
#define _GLIBCXX_DEQUE_BUF_SIZE 512
static size_t _S_buffer_size() _GLIBCXX_NOEXCEPT
{ return __deque_buf_size(sizeof(_Tp)); }inline size_t
__deque_buf_size(size_t __size)
{ return (__size < _GLIBCXX_DEQUE_BUF_SIZE
? size_t(_GLIBCXX_DEQUE_BUF_SIZE / __size) : size_t(1)); }
下面再来看下operator--的实现,和++相反,这里回去判断当前元素的指向是否超出当前缓冲区的第一个元素的位置:
_Self&
operator--() _GLIBCXX_NOEXCEPT
{
if (_M_cur == _M_first)
{
_M_set_node(_M_node - 1);
_M_cur = _M_last;
}
--_M_cur;
return *this;
}
迭代器 += 的实现
迭代器的 += 实现随机存取,迭代器可以直接跳跃n个距离,实现如下:
_Self&
operator+=(difference_type __n) _GLIBCXX_NOEXCEPT
{
const difference_type __offset = __n + (_M_cur - _M_first);//计算_M_cur加n之后的偏移值
if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))//判断_M_cur是否超出缓冲区的范围
_M_cur += __n;//如果没有超出缓冲区的返回,直接对_M_cur 加 n
else
{//如果超出该缓冲区的范围执行如下操作
const difference_type __node_offset =
__offset > 0 ? __offset / difference_type(_S_buffer_size())
: -difference_type((-__offset - 1)
/ _S_buffer_size()) - 1;//计算中控器中节点(也就是缓冲区)的偏移,从这个可以看出n可以是负值
_M_set_node(_M_node + __node_offset);//设置缓冲区的指针,指向+n 之后落到的缓冲区
_M_cur = _M_first + (__offset - __node_offset
* difference_type(_S_buffer_size()));//设置迭代器指向+n指向实际指向元素
}
return *this;
}
deque的内存管理
在了解deque内存管理之前来看下deque size的获取:
size_type
size() const _GLIBCXX_NOEXCEPT
{
return this->_M_impl._M_finish - this->_M_impl._M_start;//简单的两个迭代器相减,结合上图理解
}
template<typename _Tp, typename _Ref, typename _Ptr>
inline typename _Deque_iterator<_Tp, _Ref, _Ptr>::difference_type
operator-(const _Deque_iterator<_Tp, _Ref, _Ptr>& __x,
const _Deque_iterator<_Tp, _Ref, _Ptr>& __y) _GLIBCXX_NOEXCEPT
{
// 首先将_S_buffer_size()获取到的缓冲区元素数目转为difference_type类型
// 然后将计算两个迭代器之间的缓冲区的数目
// 最后加上两个迭代器指向的缓冲区实际的元素像素
return typename _Deque_iterator<_Tp, _Ref, _Ptr>::difference_type
(_Deque_iterator<_Tp, _Ref, _Ptr>::_S_buffer_size())
* (__x._M_node - __y._M_node - 1) + (__x._M_cur - __x._M_first)
+ (__y._M_last - __y._M_cur);
}
构造函数
explicit
deque(size_type __n, const value_type& __value = value_type(),
const allocator_type& __a = allocator_type())
: _Base(__a, __n)
{ _M_fill_initialize(__value); }
调用_M_fill_initialize负责设置初值:
template <typename _Tp, typename _Alloc>
void
deque<_Tp, _Alloc>::
_M_fill_initialize(const value_type& __value)
{
_Map_pointer __cur;
__try
{
for (__cur = this->_M_impl._M_start._M_node;
__cur < this->_M_impl._M_finish._M_node;
++__cur)
std::__uninitialized_fill_a(*__cur, *__cur + _S_buffer_size(),
__value, _M_get_Tp_allocator());//为每个缓冲区设置初值
std::__uninitialized_fill_a(this->_M_impl._M_finish._M_first,
this->_M_impl._M_finish._M_cur,
__value, _M_get_Tp_allocator());//最后一个节点稍有不同,因为可能有备用空间,不设初值
}
__catch(...)
{
std::_Destroy(this->_M_impl._M_start, iterator(*__cur, __cur),
_M_get_Tp_allocator());
__throw_exception_again;
}
}
父类_Base(__a, __n)负责deque的结构设置:
_Deque_base(const allocator_type& __a, size_t __num_elements)
: _M_impl(__a)
{ _M_initialize_map(__num_elements); }
_M_initialize_map的实现如下:
template<typename _Tp, typename _Alloc>
void
_Deque_base<_Tp, _Alloc>::
_M_initialize_map(size_t __num_elements)
{
const size_t __num_nodes = (__num_elements/ __deque_buf_size(sizeof(_Tp))
+ 1);//计算需要节点(缓冲区)的数目//map管理的节点最少8个,如果大于8个要预留前后个一个节点用于扩充
this->_M_impl._M_map_size = std::max((size_t) _S_initial_map_size,
size_t(__num_nodes + 2));
this->_M_impl._M_map = _M_allocate_map(this->_M_impl._M_map_size);//分配节点指针_Tp**// For "small" maps (needing less than _M_map_size nodes), allocation
// starts in the middle elements and grows outwards. So nstart may be
// the beginning of _M_map, but for small maps it may be as far in as
// _M_map+3.//设置_nstart 为起始节点,注意对扩充节点的处理
//另外使start 和 finish 处于map的中间位置
_Tp** __nstart = (this->_M_impl._M_map
+ (this->_M_impl._M_map_size - __num_nodes) / 2);
_Tp** __nfinish = __nstart + __num_nodes;//设置__nfinish为最后节点__try
{ _M_create_nodes(__nstart, __nfinish); }//为所有的节点配置缓冲区
__catch(...)
{
_M_deallocate_map(this->_M_impl._M_map, this->_M_impl._M_map_size);
this->_M_impl._M_map = 0;
this->_M_impl._M_map_size = 0;
__throw_exception_again;
}this->_M_impl._M_start._M_set_node(__nstart);
this->_M_impl._M_finish._M_set_node(__nfinish - 1);
this->_M_impl._M_start._M_cur = _M_impl._M_start._M_first;
this->_M_impl._M_finish._M_cur = (this->_M_impl._M_finish._M_first
+ __num_elements
% __deque_buf_size(sizeof(_Tp)));
}
_M_create_nodes的实现:
template<typename _Tp, typename _Alloc>
void
_Deque_base<_Tp, _Alloc>::
_M_create_nodes(_Tp** __nstart, _Tp** __nfinish)
{
_Tp** __cur;//cur为指针,指向缓冲区,缓冲区有 n 个 _Tp
__try
{
for (__cur = __nstart; __cur < __nfinish; ++__cur)
*__cur = this->_M_allocate_node();//遍历所有的节点,分配缓冲区
}
__catch(...)
{
_M_destroy_nodes(__nstart, __cur);
__throw_exception_again;
}
}
_M_allocate_node的实现源码如下:
_Tp*
_M_allocate_node()
{//调用配置器分配缓冲区内存
return _M_impl._Tp_alloc_type::allocate(__deque_buf_size(sizeof(_Tp)));
}
最后更新迭代器为正确的信息:
this->_M_impl._M_start._M_set_node(__nstart);
this->_M_impl._M_finish._M_set_node(__nfinish - 1);
this->_M_impl._M_start._M_cur = _M_impl._M_start._M_first;
//_M_finish._M_cur指向最后一个元素的下一个位置
this->_M_impl._M_finish._M_cur = (this->_M_impl._M_finish._M_first
+ __num_elements
% __deque_buf_size(sizeof(_Tp)));
push_back的实现
push_back的源码:
void
push_back(const value_type& __x)
{
// 首先判断是否有空间,有的话直接插入
if (this->_M_impl._M_finish._M_cur
!= this->_M_impl._M_finish._M_last - 1)
{
this->_M_impl.construct(this->_M_impl._M_finish._M_cur, __x);
++this->_M_impl._M_finish._M_cur;
}
else//没有空间调用此处
_M_push_back_aux(__x);
}
_M_push_back_aux的实现如下:
执行过程如下,首先判断当前map的节点是否满足使用,在之前的构造函数中知道map会做预留,如果map的节点不能满足当前使用,扩展节点——>接着分配节点的内存——>构造元素——>调整迭代器
template<typename _Tp, typename _Alloc>
void
deque<_Tp, _Alloc>::
_M_push_back_aux(const value_type& __t)
{
_M_reserve_map_at_back();
*(this->_M_impl._M_finish._M_node + 1) = this->_M_allocate_node();
__try
{
this->_M_impl.construct(this->_M_impl._M_finish._M_cur, __t);
this->_M_impl._M_finish._M_set_node(this->_M_impl._M_finish._M_node
+ 1);
this->_M_impl._M_finish._M_cur = this->_M_impl._M_finish._M_first;
}
__catch(...)
{
_M_deallocate_node(*(this->_M_impl._M_finish._M_node + 1));
__throw_exception_again;
}
}
对于边界的处理如下图所示(如果当前缓冲区使用完,finish迭代器会指向下一个节点,即缓冲区):
push_front的实现
push_front为在前端插入元素,其实现如下:
void
push_front(const value_type& __x)
{
if (this->_M_impl._M_start._M_cur != this->_M_impl._M_start._M_first)
{//如果当前缓冲区未使用完,直接使用
this->_M_impl.construct(this->_M_impl._M_start._M_cur - 1, __x);
--this->_M_impl._M_start._M_cur;
}
else
_M_push_front_aux(__x);//调用_M_push_front_aux申请新的节点
}
_M_push_front_aux的实现:
template<typename _Tp, typename _Alloc>
void
deque<_Tp, _Alloc>::
_M_push_front_aux(const value_type& __t)
{
_M_reserve_map_at_front();
*(this->_M_impl._M_start._M_node - 1) = this->_M_allocate_node();
__try
{
this->_M_impl._M_start._M_set_node(this->_M_impl._M_start._M_node
- 1);//更新_M_start的节点信息
this->_M_impl._M_start._M_cur = this->_M_impl._M_start._M_last - 1;//指向新节点的最后一个元素位置this->_M_impl.construct(this->_M_impl._M_start._M_cur, __t);//构造元素
}
__catch(...)
{
++this->_M_impl._M_start;
_M_deallocate_node(*(this->_M_impl._M_start._M_node - 1));
__throw_exception_again;
}
}
_M_reserve_map_at_front的实现:
void
_M_reserve_map_at_front(size_type __nodes_to_add = 1)
{//首先判断是否有未使用的节点,没有申请分配
if (__nodes_to_add > size_type(this->_M_impl._M_start._M_node
- this->_M_impl._M_map))
_M_reallocate_map(__nodes_to_add, true);
}
边界处理示意图:
[_其它容器实现](https://blog.youkuaiyun.com/qq_25065595/category_10248097.html):