代码改变世界

STL源码剖析之序列式容器Deque

2011-06-09 19:10  Aga.J  阅读(1839)  评论(0编辑  收藏  举报

37 deque

    Vector是单向开口连续线性空间,用户只能在vector尾部进行插入删除操作。(当然它也允许在某个pos处插入元素,但是要注意到Vector的底层实现是数组,所以过多或者频繁的在非队尾的位置插入会有性能上的消耗

    而deque是一种双向开口连续线性空间,允许我们在头尾两端操作。所以我们需要保证deque可以在常数时间内对头和尾元素进行插入或者删除

    Deque还有一个明显的与Vector不同的特点,就是它没有capacity的概念,它是动态地用分段连续的空间组合起来的,随时可以增加一段新的空间并连接起来,而为了隐藏deque的这些底层细节(分段连续的空间组成---需要一个中央管理器来管理这些空间),deque设计者必须让用户感觉在使用一个整体连续的对象,并且提供了随机存取的接口,这样一来,就需要复杂的迭代器结构

如果要我们设计这样的容器的迭代器,满足上面的条件,我们要怎么设计??

1 可以同时在头尾操作

答:可以使用分段的线性空间来完成

2 随机存取

答:以连续线性空间为基础,进行分段空间的管理

 

38 deque所使用的管理分段的连续空间的中央管理器map

Map存放的是指向连续线性空间的缓冲区的指针。

Template<class T, class Alloc=alloc, size_t BufSize=0 >

Class deque

{

Public:

Typedef T value_type;

Typedef value_type* pointer;

//其他typedef….

 

Protected:

Typedef pointer* map_pointer;    //指向元素指针类型的指针(元素为指针类型的数组)

 

Protected:

Map_pointer map;                     // T** map

Size_type map_size;

};

 clip_image002

 

39 deque的迭代器

Deque的迭代器比较特殊,它需要知道各个分段连续空间在哪里,而且还必须在++或者—操作中正确跳转到下一个或前一个分段连续空间,这样一来,deque的迭代器就需要知道map的数据信息

初步的deque 迭代器定义:

Template<class T, class Ref, class Ptr, size_t BufSize>

Struct __deque_iterator

{

Typedef __deque_iterator<T,T&,T*,BufSize> iterator;

Typedef __deque_iterator<T,const T&, const T*> const_iterator ;

Typedef __deque_iterator self ;

Static size_t buffer_size() {return __deque_buf_size(BufSize,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;               //需要维护MAP的信息

T* cur;

T* first;

T* last;

Map_pointer node ; //还需要维护这个map的节点信息来提供给用户一致的指针操作

//........

}

最后关于迭代器中的 中控器map缓冲区迭代子之间的关系如下图所示的设计;

 clip_image004

    迭代器需要维护map容器的指针,便于对缓冲区的跳转访问,而cur则指向当前缓冲区内的当前节点,因为需要解决迭代器中的指针何时跳转的问题,所以每个迭代器需要维护一个first和last的哨兵(可以在中继器中相应的缓冲区节点维护这两个位置吗?或者在某一级buffer中维护?如果只在迭代器中维护,那开销会小很多!)

 

Deque迭代器的详细设计:

因为iterator都只维护目前的缓冲区的信息(如first,last,node),单一的iterator无法完整完成整个deque的迭代子操作(跳转,begin,last等)

{

Void set_node(map_pointer new_node)        //设置new node为当前的缓冲区指针,并初始化该缓冲区的first和last

{

Node= new_node;

First=*new_node;

Last = first + difference_type(buffer_size);

}

Reference operator*() const { return *cur;}            //取值

Pointer operator->() const{return &(operator*());}

Difference_type operator – (const self& x)const       // a-b

{

Return (difference_type ) ( (cur-first)+(x.last-x.curr)+(this.node-x.node-1)*buffer_size);

}

Self& operator++()                                             //++操作只对iterator内部的curr进行修改。

{

If (curr==last) //如果已经是最后一个节点了,那就跳转,调用setNode完成

{

  Set_node(node+1);

  Curr=first; //到达新的node的first

}

Else

  ++curr;

Return *this;

}

Self operator++(int) //后置++,返回一个实际值,而不是引用。i++

{

Self tmp=*this;

++*this;                   //调用this的前置++

Return tmp;

}

Self& operator += (difference_type n)

//返回引用,支持连加,这个函数的实现还支持-=,也就是传入的n为负数

{

Difference_type offset= n+ cur-last;

If(offset>0 && offset< difference_type( buffer_size() )               //当前段可以容纳新增的n个元素

  Curr += n;

Else

{

  Difference_type node_offset = offset>0? Offset/difference_type(buffer_size()): -difference_type( (-offset-1)/buffer_size())-1;

  Set_node(node+node_offset); //根据计算跳转到新的node上

  Curr= first+(offset-node_offset*difference_type(buffer_size()));

}

Return *this;

}

Self operator+(difference_type n) const              //+号返回对象,不支持连加,const函数的使用更加准确

{

Self tmp=*this;

Return tmp+=(n);  

因为要返回一个实际对象,而且并不是修改当前的对象(+=返回的是引用),所以需要先拷贝当前的对象到一个临时对象上,然后再返回这个临时对象

}

Self& operator-=(difference_type n)

{

Return *this+=(-n); //由于+=的实现,使得-=可以很容易的实现

}

Self operator –(defference_type n)

{

Self tmp=*this;

Return tmp-=(n);

}

Reference operator[](difference_type n) const{ return *( *this+n) ;}

//调用this的+号操作,完成底层指针位置偏移,再调用*,返回实际对象

//== return *(curr+n)

//== = <

}

 

40 deque 的详细数据结构

Template<class T,class Alloc=alloc, size_t buffsize=0>

Class deque

{

Typedef T value_type;

Typedef value_type* pointer;

Typedef size_t size_type;

 

Public:

Typedef __deque_iterator<T,T&,T*,Bufsize> iterator;

 

Protected:

Typedef pointer* map_pointer;

 

Protected:

Iterator start;                          //__deque_iterator<T,T&,T*,Bufsize>

Iterator finish;

Map_pointer map;

Size_type map_size;

//..

 

Public:

Iterator start(){return start;}

Iterator end(){return finish;}

Reference operator[](size_type n)

{

Return start[ (difference_type)n ];

}

Reference front(){ return *start;}

Reference back()

{

Iterator tmp= finish;

--tmp;

Return *tmp;

//为什么不可以改为 return *(finish-1); 好像可以

}

Size_type size() const { return finish – start;}

Size_type max_size() const { return size_type(-1);}

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

};

 

41 Deque的构造和内存管理,元素操作管理 ctor, push_back,push_front

Deque的使用:

Deque<int,alloc,32> ideq(20,9);                        //为了指定deque的第三个参数,必须给出第二个参数

                                                                    //构造一个deque,有20个int元素,初始值为9,其中deque每个缓冲区大小为32

Push_back()

Push_front()

Deque<int ,32>::iterator itr;

Itr= find( ideq.begin(), ideq.end(), 99);              //使用算法find从deque的起始迭代器到末尾迭代器找到99

 

Deque详解:

从构造函数开始

Protected:

Typedef simple_alloc<value_type, Alloc> data_allocator;

element底层分配器基本单位是value_type

Typedef simple_alloc<pointer,Alloc> map_allocator;

map底层分配器的基本单位类型是pointer

Deque(int n, const value_type& value)                 //使用value初始化n个元素

: 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( int n,const value_type& value)

{

//计算需要多少个map节点,初始化map

//初始化first,执行初始化

//初始化last

Int mapNodeCapacity= BufSize / (size_t)(sizeof(value_type));

//根据value的字节大小和缓冲区允许的字节大小来计算每个缓冲区大小

Int mapNodeNum= n/mapNodeCapacity +1 ;

//计算得到需要多少个缓冲区,map的节点个数

Map_allocator.allocate(mapNodeNum);                                // typedef simple_alloc<pointer,alloc> Map_allocatior;

//设置迭代器start和迭代器last

Create_map_and_nodes(n);                                                //把deque的结构产生并安排好

Map_pointer cur;

__STL_TRY

{

For( cur=start.node;cur<finish.node;++cur)

  Uninitialize_fill( * cur, *cur+ buffer_size(), value);

 

Uninitialized_fill( finish.first, finish.cur,value); //最后一个节点有备用空间

}

Catch(){}

}

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;

Map_size = max(initial_map_size(), num_nodes+2);

Map = map_allocator::allocate(map_size);

//另nstart和nfinish都指向map所拥有的所有节点的最中央的区段

//这样可以使得在扩充的时候头尾扩充幅度一致。

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)

      *cur= allocate_node(); //分配节点,大小为缓冲区大小

}

Catch(){}

Start.set_node(nstart);

Finish.set_node(nfinish);

Start.cur=start.first;

Finish.cur= finish.first + num_elements & buffer_size();

}

//计算map节点数

//完成map的构造,缓存器指针构造

//按照要求从start开始初始化节点

 

42 deque的push_back操作

Void push_back(const value_type& t)

{

If ( finish.cur!= finish.last -1) //最后的缓冲区还有一个以上的备用空间

{

  Construct( finish.cur, t);

  ++finish.cur;

}

Else

  Push_back_aux(t); //无空间或者只剩下一个,进行拓展性插入

}

Template<class T, class Alloc, size_t bufsize>

Void deque<T, Alloc, bufsize>::push_back_aux (const value_type& t)

{

//分配新节点

//初始化节点

//重定向指针

( Finish.node+1 ) = allocate_node() ; //错误,finish.node+1是一个指向map容器的指针,类型为map容器的节点类型,*(finish.node+1)是map容器内的元素,也就是指向缓冲区的指针,所以是

*(finish.node+1) = allocate_node() ;

__STL_TRY

{

Construct( finish.cur, t_copy) ; //设值

Finish.set_node(finish.node+1) ; //finish指向新的map的node上!

Finish.cur=finish.first ;

}

__STL_UNWIND( deallocate_node(*(finish.node)+1))) ;

}

43 deque的push_front操作

Void push_front(const value_type& t)

{

If ( start.cur != start.first)

{

  Construct ( start.cur -1 ,t); //在start的cur的前一个备用空间商构造元素

  --start.cur;

}

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)

{

Try{

*(Start.node-1)=allocate_node() ;

Start.set_node( start.node-1) ;

Start.cur= start.last -1 ; //?? -1??

Construct(start.curr, t_copy ) ;

}

Catch()

{

Start.set_node( start.node+1);

Start.cur= start.first;

Deallocate_node(*(start.node-1));

}

}

//注意,如果map节点在push front 和push back时都不够了,那么就需要重新分配一个更加大的map 

43 

Reserve_map_at_back( size_type node_to_add=1)        //在map最后预留一个node

{

If( nodes_to_add +1 > map_size – (finish.node-map) )  //大于剩余的尾部空node

  Reallocate_map( nodes_to_add, false);

}

 

 

Reserve_map_at_front( size_type node_to_add=1)

{

  If( nodes_to_add > start.node- map)                            //大于剩余的头部空node

    Reallocate_map (nodes_to_add,true);

}

 

Template< class T ,class Alloc, size_t BufSize>

Void deque< T, Alloc,BufSize>::reallocate_map( size_type nodes_to_add, bool add_at_front)

{

  Size_type old_num_nodes = finish.node – start.node+1;

  Size_type new_num_nodes = old_num_nodes+ nodes_to_add;

  Map_pointer new_nstart;

  If( map_size > 2*new_num_nodes)

  {

      New_nstart = map + ( map_size – new_num_nodes)/2 + ( add_at_front ? nodes_to_add:0);

      If( new_nastart<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;

    Map_pointer new_ map = map_allocater::allocate(new_map_size);

    New_nstart = new_map + (new_map_size- new_num_nodes)/2+(add_at_front?nodes_to_add:0);

    Copy(start.node,finish.node+1,new_nstart);

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

}

 

44 pop_back,pop_front

Void pop_back()

{

  If(finish.cur!=finish.first)

  {

    Destroy(finish.cur);

    --finish.cur;

  }

  Else

    Pop_back_aux();         //被删的节点刚好是最后一个缓冲区的第一个节点

}

 

Template<class T, class Alloc, size_t BufSize>

Void deque<T,Alloc,BufSize> ::pop_back_aux()

{

   Deallocate_node(finish.first) ;

设计上最好不要删除最后一个缓冲区,但是实际操作中需要这样做才能在下次正确的找到最后一个元素所在的缓冲区。

   Destroy(finish.cur) ;

   Finish.setNode( finish.node-1 ) ;

   Finish.cur=finish.last-1 ;

}

 

Void pop_front()

{

  If( start.cur != start.last-1) //如果要删除的是当前缓冲区的最后一个元素

  {

    Destroy(start.cur) ;

    ++start.cur ;

  }

  Else

   pop_front_aux() ;

}

 

Template<class T, class Alloc, size_t Bufsize>

Void deque<T, Alloc, BufSize> :: pop_front_aux()

{

  Destroy(start.curr) ;

  Deallocate_node( start.first) ;

  Start.set_node(start.node+1) ;

  Start.cur=start.first;

}

 

45 clear erase

Template<Class T, class Alloc, size_t BufSize>

Void deque<T, Alloc, BufSize> ::clear()

{

For( map_pointer node= start.node+1; node<finish.node;++node)

//这里的删除只针对头尾以外两个缓冲区,因为其中的内容一定是满的

{

   Destroy(*node, *node+buffer_size()); //先删除每个节点里面的东西

   Data_allocator::deallocate(*node,buffer_size()); //在释放整个缓冲区

只能先删除节点里面的东西,再释放整个缓冲区,不然可能会造成内存泄露

}

 

If(start.node!=finish.node)

{

  Destroy( start.cur,start.last);

  Destroy(finish.first, finish.cur);

  Data_allocator::deallocate(finish,first, buffer_size());

}

Else

{

  Destroy(start.curr, finish.curr);

  Finish=start;

}

}

Iterator erase(iterator pos)

{

  Iterator next = pos;

  ++next;

  Difference_type index= pos-start; //要删除的pos到start的距离

  If( index< (size()>>1) )

如果小于原来大小的一半,那么就移动前面的,很巧妙的用法

{

  Copy_backward( start, pos, next);

  Pop_front();

}

Else

{

  Copy(next, finish,pos);

  Pop_back();

}

Return start+index;

}

//erase( iterator first, iterator last)

//insert(iterator position,const value_type& x)

  小结:

  deque的设计像是融合了vector和list,设定 多个缓冲区,每个缓冲区大小一致,然后通过一个容器存储这些缓冲区的首地址。并使得deque可以简单的完成push front或push back等操作。这对deque的迭代器要求很大,同时其他操作的要求也很复杂。

 

  在deque类中,使用T** map保存指向缓冲区的指针

  而deque迭代器的设计,大部分基本类别信息的定义和普通迭代器一致(为了完成traits编程)。同时该迭代器(该指向缓冲区的数据结构)保存了缓冲区curr,first,last的位置信息,更重要的是它维护了一个指向deque类的map指针,表示该迭代器本身指向那个map容器的那个指针,这样一来,我们就可以根据这个指针,调整缓冲区的指向。

  然后迭代器的设计仍然遵守普通迭代器设计,重载了*,->,++,--等操作,满足了map容器上的缓冲区指针移动。

  而在deque的设计中,除了维护map,还要有start,和finish两个哨兵迭代子

  最后就是deque的构造和deque的基本操作,这些都依赖于迭代器来完成,具体则涉及到迭代子的内部指针的调整以应对迭代器删除或者增加。