STL源码剖析之deque

之前我们学了stl中的内存分配器alloc,也学习了typetraits技巧,并且从vector和list的学习中对这两个技巧有了进一不的理解,现在我们要学deque,他本质上没有多难。


关于deque

deque是一个双端开口的连续空间,可以随意在队头或者队尾插入元素,注意虽然他叫做deque,有double-ended-queue的意思,但是deque却可以随机访问哦。
deque的实现完美地展现了iterator的强大之处,通过各种各样的重载将deque对外完全表现成了一个双端开口的连续空间,真的很厉害,我们来看看吧。


一、deque的中控器

直接开讲!deque是将多个分段连续空间给拼凑起来的,我们很自然就可以想到需要有一个东西来处理这些分段连续的空间,这个东西称为map

template <class T, class Alloc = alloc, size_t BufSize = 0>
class deque{
public:
	typedef T value_type;
	typedef value_type*		pointer;
protected:
	typedef pointer* map_pointer'
private:
	map_pointer map;
	size_type map_size;
}

可以看出来我们的中控器map是一个二级指针,他管理着的空间里面包含着指针,而这些指针又指向一个个内存空间,可以用建图中的链式前向星来理解。

二、deque的迭代器

deque要伪装出一个整体连续的现象,但是map所指向的连续空间两两之间并不连续,所以我们要对iterator做出相应调整,使之完成伪装成连续

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator{
	//这里定义了iterator
	typedef __deque_iterator<T,  T&, T*, BufSiz>				iterator;
	typedef __deque_iterator<T, const T&, const T*, BufSiz>		const_iterator;
	static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T));}

	typedef random_access_iterator_tag 	iterator_category;
	typedef T							value_type;
	typedef Ptr							pointer;
	typedef Ref							reference;
	typedef size_t						size_type;
	typedef ptrdiff_t					difference_type;
	typedef T**							map_pointer;

	typedef __deque_iterator			self;

	T*		cur;
	T*		first;
	T*		last;
	map_pointer		node;
	//很可爱的迭代器设计,除开迭代器必须要有的几大元素之外,还需要有为了适配deque而需要确立的几点
	//首先是begin和last的设计,指向当前deque的itr的cur所在连续区间的首尾,deque的iterator为什么需要知道首尾呢,因为在++,--的过程中我们不能想vector,list之类的迭代器可以无法阻挡地进行,deque必须防范出现之类的就必须给iterator指明我们的first和last。

	inline size_t __deque_buf_size(size_t n, size_t sz){
		return n != 0 ? n : (sz  < 512 ? size_t(512 / sz) : size_t(1));
	}
	//上述代码是未手动设置bufsize大小时对bufsiz的处理
}

可以看出迭代器的基本结构就是用迭代器指向每一个连续空间,同时还有指回中控器map的节点,为了防止超出空间边界,迭代器还有关于连续空间首位的相关信息,当然也有自己指向哪里的信息。我们的迭代器在进行跳跃时便会更改自己的cur,但不仅仅于此,当我们跳跃到当前连续空间的尽头时我们就有的事做了,我们一定会想到我们要在map中更改自己所在的连续空间。

void set_node(map_pointer new_node) {
	//更改在map中所在的位置。那么我们会更改我们的node,first,last,但这个函数只是作改变node,没有变化cur捏
	node = new_node;
	first = *new_node;
	last = *new_node + difference_type(buffer_size());
}

可以看出deque的实现中我们实现迭代器跳跃时总是危机暗藏。而我们的++,–,+=,-=是在刀尖上起舞,我们必须要重载这些东西,以保证我们的程序按照我们所想来运行。
现在是,重载时刻!

reference operator *()const {return *cur;}
pointer	operator->() const {return &(operator*());}
difference_type operator-(const self& x) const {
	//这里距离的测量细节需要自己好好验算下哦
	return difference_type(buffer_size()) * (node - x.node - 1) + (cur - first) + (x.last - x.cur);
}

self& operator++() {
	++cur;
	if(cur == last){
		set_node(node + 1);
		cur = first;
	}
	return *this;
}

self operator++(int){
	self tmp = *this;
	++(*this);
	return tmp;
}

self& operator--(){
	if(cur == first){
		set_node(node - 1);
		cur = last;
	}
	return *this;
}

self operator--(int){
	self tmp = *this;
	--(*this);
	return tmp;
}

//为什么+=得到的是一个左值呢,emm,很显然!
self& operator +=(difference_type n){
	//cur - first,距离计算这里需要注意,cur所指向的地方是没内容的,具体可以看push_back的实现
	//first是有内容的,所以first 到 cur就相当于cur - first + 1 - 1
	difference_type offset = n + cur - first;
	//说明是在一个连续空间中嗨皮,那就嗨皮吧
	if(offset >= 0 && offset < difference_type(buffer_size()){
		//因为在连续空间中,所以每个指针都是随机访问的,直接调用最基本的运算就行了
		cur += n;
	}else{
		//要注意offset很有可能是一个小于0的值,这个就需要特殊考虑
		if(offset < 0){
			//-offset是将负变正,但是除以buffersize后得到的只是从0开始的值,而我们的offset一旦小于零
			//是肯定要转移node的,所以这时候我们的nodeoff应该是要从1开始
			//setnode之后会修改我们的
			difference_type nodeoff = -offset / difference_type(buffer_size()) - 1;//代码写的有点问题,也懒得改了
			//需要认真验算下这其中的运算过程
			cur = first + (offset -  nodeoff * difference_type(buffer_size());
		}else{
			difference_type nodeoff = offset / difference_type(buffer_size());
			difference_type nodegap = offset % difference_type(buffer_size());
			set_node(node + nodeoff);
			cur = first + nodegap;
		}
	}
	return *this;
}

self operator+(difference_type n) const {
	self	tmp = *this;
	return 	tmp += n;
}

self& operator-=(difference_type n){
	*this += -n;
	return *this;
}

self operator -(difference_type n) const{
	self tmp = *this;
	tmp -= n;
	return tmp;
}

reference operator[](difference_type n) {return *(*this + n);}
reference operator !=(const self& x)const {
	return cur != x.cur;
}

reference operator ==(const self& x)const {
	return cur == x.cur;
}

bool operator<(const self& x)const{
	return	node == x,node ? cur < x.cur : node < x.node;
}

上面的代码显示出我们的迭代器为了能在分段连续的空间中对外表现出连续的性质,我们将+,-等都进行了重载,最为重要的是当出现我们的+,-等运算操作时,我们就必须对map中的节点进行修改。这里面一些位移细节需要仔细地运算。

讲完了迭代器,我们在说下容器的操作把!

deque的容器整体内容

deque的成员很简单,一个first,last表现首位,map操作中控器,再来一个中控器的大小,就是如此!看代码喵!

template<class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:
	typedef T					value_type;
	typedef value_type* 		pointer;
	typedef size_t				size_type;
public:
	
	typedef __deque_iterator<T, T&, T*, BufSiz>		iterator;
//什么是protected关键字?就是他的继承类内部类能够使用它,但是实例化他的继承类后无法通过示例来调用
//应该能理解成可以被继承成private的东西,但是子类的子类还是能获得其protected的内容
//也可以认为protected的东西在继承后只能通过类的this指针相关来进行访问
protected:
	typedef pointer*			map_pointer;//typedef 也是可以被访问控制修饰符修饰的诶
protected:
	iterator					start;
	iterator					finish;
	map_pointer					map;
	size_type					map_size;

	//一个容器很应该需要知道首尾相关的内容
	iterator begin()	const{
		return start;
	}
	
	iterator end()		const{
		return finish;
	}

	reference operator[](size_type n)	const{
		return *start[difference_type(n)];
	}

	reference front() const{
		return *start;
	}

	reference back() const{
		iterator tmp = finish;
		--tmp;
		//因为iterator是对指针的包装,所以就算tmp是一个临时变量,*tmp得到的确实一个正确的引用哦
		return *tmp;
	}

	size_type	size() const {return finish - start;}
	//这里能想到-1因为无符号数能够取到最大值,然后转换成class的size就能得到最大数量
	//但是这个-1对应的最大值真的是操作系统所能分配的最大值吗
	//其中的具体细节还需要以后慢慢学习
	size_type	max_size() const{return size_type(-1);} 

	bool empty()const{return start == finish;}

}

上面是deque容器的一些基本返回信息,但对用户来说,使用deque是对元素进行相关操作的,这才是deque对外的重点内容

//对元素进行操作,首先就是要构建元素,构建元素就涉及空间分配和构造函数相关内容,看看吧
//alloc第一个参数是在该空间上存储的内容的类型
typedef simple_alloc<value_type	, Alloc> data_allocator;
typedef simple_alloc<pointer	, Alloc> map_allocator;

//一种构造函数
deque(size_type n, const value_type& value): start(), finish(), map(0), map_size(0){
	//分配空间并设定初值
	fill_initialize(n, value);
}


//模板类实现他的函数的时候需要把模板参数写出
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::fill_initialize(size_type n, const value_type& value){
	create_map_and_nodes(n);
}

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements){
	size_type num_nodes = num_elements / buffer_size() + 1;
	//initial_map_size为8哦
	map_size 			= max(initial_map_size(), num_nodes + 2);
	map   				= map_allocator::allocate(map_size);

	//map_size - num_nodes的值是向下取整的
	map_pointer		nstart 	= map + (map_size - num_nodes) / 2;
	map_pointer		nfinish	= nstart + num_nodes - 1;
	map_pointer     cur;	
	_STL_TRY{
		for(cur = nstart; cur != nfinish; ++cur){
			//allocate_node的就是分配连续地址,这一段代码可以认为是和vector的连续空间分配是一样的
			//猜测他的源代码大概是调用data_allocator分配buffersize大小的空间,然后将首地址返回给*cur
			*cur = allocate_node();
		}
	}
	start.set_node(nstart);
	finish.set_node(nfinish);
	start.cur 	= start.first;
	finish.cur 	= finish.first + num_element % buffer_size();
}

public:
	void push_back(const value_type& t){
		if(finish.cur != finish.last - 1){
			//注意我们的设计准则是左闭右开,所以finish.cur指向的便是最近的空点
			construct(finish.cur, t);
			++finish.cur;
		}else {
			push_back_aux(t);
		}
	}
	
	//出现了新的不知道的东西
	template <class T, class Alloc, size_t BufSize>
	void push_back_aux(const value_type& t){
		value_type t_copy = t;
		reserve_map_at_back();
		//预先分配好内存
		*(finish.node + 1) = allocate_node();
		__STL_TRY{
			construct(finish.cur, t_copy);
			finish.set_node(finish.node + 1);
			finish.cur = finish.first;
		}
	}
//照猫画虎,我们可以完成push_front
	void push_front(const value_type& t){
		if(start.cur != start.first){
			--start.cur;
			construct(start.cur, t);
		}else
			push_front_aux(t);
	}

	template <class T, class Alloc, size_t BufSize>
	void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t){
		value_type t_copy = t;
		reserve_map_at_back();
		*(start.node - 1) = allocate_node();
		__STL_TRY{
			start.set_node(start.node - 1);
			start.cur = start.last - 1;
			construct(start.cur, t_copy);
		}
	}

	void reserve_map_at_back(size_type nodes_to_add = 1){
		//这里的细节内容是这样的
		//首先计算加上add内容后是否会超
		//令 t = finish.node - map, t的值为当前Finish到map头的数量 - 1
		//map_size - 上面的得到的就是剩下的还可以用的node数量 + 1
		//so!我们的node_to_add也需要+1哦
		//
		if(nodes_to_add  + 1 > map_size - (finish.node - map)){
			reallocate_map(nodes_to_add, false);
		}
	}

	void reserve_map_at_front(size_type nodes_to_add = 1){
		if(nodes_to_add > start.node - map){
			reallocate_map(nodes_to_add, true);
		}
	}

	template <class T, class Alloc, size_t BufSize>
	class deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add, 
													bool add_at_front){
		//这一步只是用来进行调整和重分配,并没有继续进行push_back和front之类的操作,但是他会认为这一步是必定会发生的
		//所以他会为即将要有的push_front之类的操作做好位置的调整
		size_type old_num_nodes = finish.node - start.node + 1;//数量要比距离多1
		size_type new_num_nodes = old_num_nodes + nodes_to_add;
		map_pointer new_nstart;
		if(map_size > 2 * new_num_nodes){
			//关于这里要对addfront特殊处理的考虑
			//加上新的node后我们的总数为new_num_nodes
			//但是为了前后均衡,我们将总数除以2,相当于我们增加的量相比之前头尾各出了一半的力量
			//至于为什么后面又加上那个玩意
			//因为他是要为整体负责的
			//如果之后要前插,那么必定会要在前面留下一点点空位
			new_nstart = map + (map_size - new_num_nodes) / 2 +
						(add_at_front ? nodes_to_add : 0);
			if(new_nstart < start.node)
				copy(start.node, finish,node + 1, new_nstart);	
			else 
				copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
		}else{
			size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;//重新设计node的大小,至少也会是当前*2 + 2哦
			map_pointer new_map = map_allocator::allocate(new_map_size);
			new_nstart = new_map + (new_map_size - new_num_nodes) / 2 + (add_at_front ? node_to_add : 0);
			copy(start.node, finish.node + 1, new_nstart);//为什么是finish.node + 1呢?
			map_allocator::deallocate(map, map_size);
			map = new_map;
			map_size = new_map_size;
		}
		start.set_node(new_nstart);
		finish.set_node(new_nstart + old_num_nodes - 1);
	}

剩下没讲的就是元素操作,很简单,相信我,你也能变成光

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值