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