定义
deque 即双端队列,与vector相比,此容器多出了pop_front()和push_front()这两个操作,即在首部的增删操作;而与list相比,deque多出了对容器元素的随机访问功能。
写在文章之前
网上也有很多关于deque源码分析的文章,不过大部分都是继承自侯捷《STL源码分析》中关于deque源码的讲解。鲜有VS版本 STL deque源码的讲解,现在就来看看VS版本是如何实现的。
实现原理
我自己的套路
若是让我自己实现一个简单的deque,何不分配两块内存,一块用来分配给push_back,另一块分配给push_front,若是内存不够了,就各自重新分配内存。插入或删除操作,只需移动元素即可。然后分别记录push_back与push_front的元素个数,这样就可以随机访问了。我这个思路岂不是很简单。
VS的套路
VS用到了二级指针,二级指针里存放的一级指针是连续的,每一个一级指针存放一块内存的地址,这块内存(就是数组,连续的)存放的是我们push_back与push_front的元素。
假若把所有block中一级指针指向的内存看成连续的,那么在这块大内存中,一边是push_back进来的,一边是push_front进来的。
当然了,分界点,未必是0与31号内存,也可能是6,7号内存。(deque是模板类,若是模板参数是int,那么二级指针就是int*)。
类成员变量
// TEMPLATE CLASS _Deque_val
template<class _Ty,
class _Alloc>
class _Deque_val
: public _Container_base12
{ // base class for deque to hold data
public:
_Mapptr _Map; // pointer to array of pointers to blocks
size_type _Mapsize; // size of map array
size_type _Myoff; // offset of initial element
size_type _Mysize; // current length of sequence
_Alty _Alval; // allocator object for stored elements
_Tptr_alloc _Almap; // allocator object for maps
};
在deque的基类,_Deque_val中,有上面那几个成员变量。_Map就是二级指针,我们以deque用来存放int型元素举例,那么_Map就是int**类型的变量。_Mapsize就是一级指针数量,或者说用来存放我们元素的内存块的数量。_Myoff已经在图2中标注,它就是我们第一个元素的内存号,其实它就可以起到区分push_back元素与push_front元素的作用。_Mysize就是deque中存在元素的数量。除了上面几个成员变量还有两个宏,也很重要。
// DEQUE PARAMETERS
#define _DEQUEMAPSIZ 8 /* minimum map size, at least 1 */
#define _DEQUESIZ (sizeof (value_type) <= 1 ? 16 \
: sizeof (value_type) <= 2 ? 8 \
: sizeof (value_type) <= 4 ? 4 \
: sizeof (value_type) <= 8 ? 2 \
: 1) /* elements per block (a power of 2) */
_DEQUEMAPSIZ是_Map最小的大小,_DEQUESIZ是一级指针指向内存能分配元素的个数。这块内存反正是16个字节,那么模板参数是int,那么这个内存block就可以存放4个元素喽。
扩容
当什么时候需要增加一级指针的数量呢?当_Myoff是4的倍数,并且元素数量<=容量减4 的时候,需要扩容了。
void _Growmap(size_type _Count)
{ // grow map by _Count pointers
if (max_size() / _DEQUESIZ - this->_Mapsize < _Count)
_Xlen(); // result too long
//默认扩容%50
size_type _Inc = this->_Mapsize / 2; // try to grow by 50%
//但是扩容至少_DEQUEMAPSIZ
if (_Inc < _DEQUEMAPSIZ)
_Inc = _DEQUEMAPSIZ;
if (_Count < _Inc && this->_Mapsize <= max_size() / _DEQUESIZ - _Inc)
_Count = _Inc;//扩容参数太小,就要至少_Inc
//_Myboff代表块号
size_type _Myboff = this->_Myoff / _DEQUESIZ;
//分配容纳一级指针int*的内存,返回二级指针 int**
_Mapptr _Newmap = this->_Almap.allocate(this->_Mapsize + _Count);
//_Myptr后面的内存,存放的是 push_front 元素
_Mapptr _Myptr = _Newmap + _Myboff;
//把push_front元素所对应的那些block的一级指针,拷贝到新内存(这里可不是拷贝元素哟),
//返回拷贝后,最后一个元素后面那个位置 _Myptr
_Myptr = _Uninitialized_copy(this->_Map + _Myboff,
this->_Map + this->_Mapsize,
_Myptr, this->_Almap); // copy initial to end
if (_Myboff <= _Count)//情况1
{ // increment greater than offset of initial block
//拷贝push_back元素
_Myptr = _Uninitialized_copy(this->_Map,
this->_Map + _Myboff,
_Myptr, this->_Almap); // copy rest of old
//初始化图3 左边空档处
_Uninitialized_default_fill_n(_Myptr, _Count - _Myboff,
(pointer *)0, this->_Almap); // clear suffix of new
//初始化图3 右边空档处
_Uninitialized_default_fill_n(_Newmap, _Myboff,
(pointer *)0, this->_Almap); // clear prefix of new
}
else//情况2
{ // increment not greater than offset of initial block
//拷贝push_back 一部分 元素到 图4 左边push_back处
_Uninitialized_copy(this->_Map,
this->_Map + _Count,
_Myptr, this->_Almap); // copy more old
//拷贝push_back 剩下一部分 元素到 图4 右边push_back处
_Myptr = _Uninitialized_copy(this->_Map + _Count,
this->_Map + _Myboff,
_Newmap, this->_Almap); // copy rest of old
//初始化剩出来的空档
_Uninitialized_default_fill_n(_Myptr, _Count,
(pointer *)0, this->_Almap); // clear rest to initial block
}
//释放push_front元素对应的block指针(一级指针)
_Destroy_range(this->_Map + _Myboff, this->_Map + this->_Mapsize,
this->_Almap);
//释放二级内存_Map
if (this->_Map != 0)
this->_Almap.deallocate(this->_Map,
this->_Mapsize); // free storage for old
this->_Map = _Newmap; // point at new
this->_Mapsize += _Count;
}
情况1:如图3,当新分配内存大小,足以容纳push_back元素
情况2:如图4,当新分配内存大小,不 足以容纳push_back元素,这时候就需要往分批拷贝。
push_back
push_back用到了两个宏_PUSH_BACK_BEGIN与_PUSH_BACK_END,粘贴代码直接把宏替换掉了。
void push_back(_Ty&& _Val)
{ // insert element at end
this->_Orphan_all();
if ((this->_Myoff + this->_Mysize) % _DEQUESIZ== 0 &&
this->_Mapsize <= (this->_Mysize + _DEQUESIZ) / _liqx_dequeSIZ)
_Growmap(1);
//因为push_front其实是从内存块后面往前push,所以这个操作得到的_Newoff会大于this->_Mapsize
size_type _Newoff = this->_Myoff + this->_Mysize;
//不过可以把this->_Map看出环形的,模_DEQUESIZ之后,再试看是否超过_Mapsiz,
//超过的话只需减去_Mapsiz,可以得到正确块号
size_type _Block = _Newoff / _DEQUESIZ;
if (this->_Mapsize <= _Block)
_Block -= this->_Mapsize;
//若 块 还未分配内存,分配之
if (this->_Map[_Block] == 0)
this->_Map[_Block] = this->_Alval.allocate(_DEQUESIZ);
//将值复制到内存处
_Cons_val(this->_Alval,
this->_Map[_Block] + _Newoff % _DEQUESIZ,
_STD forward<_Ty>(_Val));
++this->_Mysize;
}
push_front
push_front用到了两个宏_PUSH_FRONT_BEGIN与_PUSH_FRONT_END,粘贴代码直接把宏替换掉了。
void push_front(_Ty&& _Val)
{ // insert element at beginning
this->_Orphan_all();
if (this->_Myoff % _DEQUESIZ == 0 &&
this->_Mapsize <= (this->_Mysize + _DEQUESIZ) / _DEQUESIZ)
_Growmap(1);
//_Newoff 为要push_front的位置,push_front是往前面插,若还push_front过,那么,
//我们应该把新元素插到最后一个内存处,内存块数*每个内存块存放元素数,即即将插入内存位置
//否则使用之前记录过的_Myoff
size_type _Newoff = this->_Myoff != 0 ? this->_Myoff: this->_Mapsize * _DEQUESIZ;
//内存块号,假若现在8个内存块,一个块存放4个元素,并且是第一次push_front,那么
//即将插入位置即块7,第3个位置处。看到此处已经将_Newoff --了,那么此时_Newoff为31
size_type _Block = --_Newoff / _DEQUESIZ;
//若内存块未分配内存,分配之
if (this->_Map[_Block] == 0)
this->_Map[_Block] = this->_Alval.allocate(_DEQUESIZ);
//在块7,块中内存3处初始化我们的值
_Cons_val(this->_Alval,
this->_Map[_Block] + _Newoff % _DEQUESIZ,
_STD forward<_Ty>(_Val));
//更新新值
this->_Myoff = _Newoff;
++this->_Mysize;
}
迭代器
迭代器里面只有一个成员变量,_Myoff,即 offset of element in deque,意思就是相对于0号内存的位置,即内存号。假如现在有8个block,每个block容纳4个元素,push_front了21次,那么begin()返回的迭代器的_Myoff应该为32-21==11。11号内存位置存放着我们第一个元素。只有_Myoff++就相当于迭代器++,就可以查看后面那个内存了。
// TEMPLATE CLASS _Deque_const_iterator
template<class _Ty,
class _Alloc>
class _Deque_const_iterator
: public _Iterator_base12
{ // iterator for nonmutable deque
size_type _Myoff; // offset of element in deque
};
删除元素
iterator erase(const_iterator _First_arg,
const_iterator _Last_arg)
{ // erase [_First, _Last)
iterator _First = _Make_iter(_First_arg);
iterator _Last = _Make_iter(_Last_arg);
//_Off 要删除第一个元素相对于第一个元素的位置
size_type _Off = _First - begin();
//要删除数量
size_type _Count = _Last - _First;
//假如 要删除第一个元素 与 第一个元素 距离 小于 要 删除最后一个元素与 最后一个元素距离
//意思就是 要删除元素 更靠近 front
if (_Off < (size_type)(end() - _Last))
{ // closer to front
//把前面元素往后挪(当然了,内存是不连续的,咋挪呢?那是因为 iterator++的时候,会跳)
_Move_backward(begin(), _First, _Last); // copy over hole
//挪了之后,push_front那些前面多出来的
for (; 0 < _Count; --_Count)
pop_front(); // pop copied elements
}
else
{ // closer to back
//与上面同理
_Move(_Last, end(), _First); // copy over hole
for (; 0 < _Count; --_Count)
pop_back(); // pop copied elements
}
return (begin() + _Off);
}