VS 2010 STL deque源碼分析

定義

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進來的。

圖2

當然了,分界點,未必是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元素

圖3

 

情況2:如圖4,當新分配內存大小,不 足以容納push_back元素,這時候就需要往分批拷貝。

圖4​

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章