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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值