deque在实现时使用一连串固定大小的数组和一个书签来实现,其结构图如下:
假设此时我们存储的数据类型为T,那么Nodes数组中存储的类型便为T*,即T[]的位置。而T[]则是实际存储数据的内存位置。这种结构在empty()时,其Nodes下不会挂载任何数组,当我们需要插入元素时,会在Nodes的中间开始挂载而不是两端进行。这样便保证了deque可以进行两端的任意插入操作。插入操作时,会先判断当前数组是否还有剩余空间,如果当前数组还剩余空间则在数组空间内placement construct,否则我们需要申请一个新的数组空间并挂载,然后在新的数组空间进行插入操作。
Deque::iterator
Deque的迭代器中存储了节点的位置信息,利用这些位置信息,迭代器可以找到其previous和next,并且Deque的迭代器是一个随机访问迭代器,所以他还能够在O(1)的时间内找到相对于此节点的任意一个节点。
其包含如下数据。
// typedef _Tp* _Elt_pointer;
// typedef _Tp** _Map_pointer;
_Elt_pointer _M_cur;
_Elt_pointer _M_first;
_Elt_pointer _M_last;
_Map_pointer _M_node;
_M_node
指向上图的某一数组在nodes中对应的元素位置,用于获取当前指向的数组的具体位置。_M_first
指向数组中第一个元素,_M_last
指向数组中最后一个元素,_M_cur
指向当前的元素。
以iterator的移动操作为例进行分析。当需要对迭代器进行移动时,我们需要考虑两种情况。1、如果移动的距离很小,使得仅在数组内部进行移动时我们仅需要移动_M_cur。2、如果移动距离过大,那么我们还需要在数组间进行移动,这时我们需要计算目标数组的位置和新的_M_cur,并且重新设置_M_node
,_M_last
,_M_first
,_M_cur
_Self&
operator+=(difference_type __n) _GLIBCXX_NOEXCEPT // __n可以为负值,表示向左进行偏移
{
const difference_type __offset = __n + (_M_cur - _M_first); // 获取从_M_first 到目标位置的全部偏移量
if (__offset >= 0 && __offset < difference_type(_S_buffer_size())) // 目标位置位于同一个数组, _S_buffer_size()返回数组中包含的元素个数
_M_cur += __n; // 直接利用_M_cur进行偏移计算
else
{
const difference_type __node_offset = // 此处用于获取nodes的偏移量,公式为 偏移量 / 数组中包含元素的个数
__offset > 0 ? __offset / difference_type(_S_buffer_size()) // offset为正数时使用此方式
: -difference_type((-__offset - 1)
/ _S_buffer_size()) - 1; // 为负数时使用此
_M_set_node(_M_node + __node_offset); // 移动_M_node,其偏移量为刚才计算的__node_offset
_M_cur = _M_first + (__offset - __node_offset
* difference_type(_S_buffer_size())); // 设置_M_cur, _M_first 和 _M_last 在 _M_set_node时已经被设置完成
}
return *this;
}
void
_M_set_node(_Map_pointer __new_node) _GLIBCXX_NOEXCEPT
{
_M_node = __new_node;
_M_first = *__new_node;
_M_last = _M_first + difference_type(_S_buffer_size());
}
operator*()和operator->()证明_M_cur指向的就是实际的元素
reference
operator*() const _GLIBCXX_NOEXCEPT
{
return *_M_cur; }
pointer
operator->() const _GLIBCXX_NOEXCEPT
{
return _M_cur; }
container
Deque大量操作都依赖迭代器完成,其内部也创建了两个iterator用于指示当前已经存储元素的头和尾。内部维护的数据如下。
_M_map
指向Nodes数组,也就是索引数组。_M_map_size
存储索引数组的大小。_M_start
存储队列的首部元素迭代器,_M_finish
存储的为尾部元素迭代器。
_Map_pointer _M_map;
size_t _M_map_size;
iterator _M_start;
iterator _M_finish;
constructor
default constructor调用栈如下,在_Deque_base()构造中调用了_M_initialize_map(0),其完成了构造过程中的主要工作。
deque() : _Base() {
}
_Deque_base()
: _M_impl()
{
_M_initialize_map(0); }
_Deque_impl()
: _Tp_alloc_type(), _M_map(), _M_map_size(0),
_M_start(), _M_finish()
{
}
_M_initialize_map(__n)函数意义为在deque中分配__n个元素所需的内存空间。构造时会先初始化_M_map所指向的内存空间,在构造时存在最小构造量8。初始化数组索引之后,再去分配所需的数组空间,其分配原则为只分配所需大小,使用lazy模式。
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)) // __deque_buf_size()用于获取存储数据的数组大小
+ 1); // __num_node表示共需构造多少数组才能完全存储__num_elements数据
this->_M_impl._M_map_size = std::max((size_t) _S_initial_map_size, // enum { _S_initial_map_size = 8 }; 此为常量 8
size_t(__num_nodes + 2)); // _M_map数组大小至少为8,即存在最小构造量
this->_M_impl._M_map = _M_allocate_map(this->_M_impl._M_map_size); // 为_M_map分配存储空间
// 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.
_Map_pointer __nstart = (this->_M_impl._M_map
+ (this->_M_impl._M_map_size - __num_nodes) / 2); // 此计算位置的结果为_M_map的中间位置 - __num_nodes / 2
_Map_pointer __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); // 发生异常,回收_M_map分配的内存
this