SGI-STL简记(六)-序列容器(deque)

deque:
  双端队列,相对vector的连续空间,deque为分段的连续空间,即一个称为map(不是map容器,而只是一片缓存而已)的连续空间,而其每个元素内容分别为一个指向一个连续缓冲区的指针,
  这些缓冲区便是真正存储数据的地方,可称为数据缓冲区;
  deque也支持随机访问,对于插入或移除操作也可在常数时间复杂度内完成,另外在deque前端或尾部增加元素时,若有必要则会分配一个较大的连续空间,再将数据放置在deque数据的前部或后面;
  除了增加元素导致需要分配较大连续空间外,若deque的map的前面或后面无法插入数据时,也回导致重新申请map连续空间,并将早期的map相关指针信息复制到新缓存,数据缓存可保持不变。
  另外为了维护所谓的连续操作,deque需维护整体连续的假象,提供随机访问的接口,故内部迭代器不可再用指针,需要特别处理迭代器,也即是迭代器的任务会比较繁重(迭代器内部有指向数据缓冲区的指针);
  对于大对象存储采取deque不太合适,因为数据缓冲区至多不能超过512字节,若是超过该大小则一个数据缓冲区仅能保持一个元素;
  deque同vector有类似的预先分配缓冲区的操作,即追加或插入一个元素时可能导致再分配一个缓冲区以存放;当再追加数据时,可在该缓冲区中直接放入新数据即可;
  此外,deque初始化状态以及调用clear后,均保留有一个数据缓冲区;当调用clear后map缓冲区空间不回收且保留一个数据缓冲区,vector调用clear后缓冲区也不会回收。

stl_deque.h : deque:一种具有双端插入和删除,可随机访问元素的容器,从首部或后插入或删除在常量时间内完成,从中间则需线性时间内完成; __deque_buf_size:获取队列节点缓冲区大小(工具函数),当数据元素类型字节size小于512时则为512
/size,否则为1,(意味着节点容器上限为512字节或者是一个自定义类型元素大小的字节); _Deque_iterator:专用于deque容器的迭代器模板类;重声明常规类型以及迭代器类型和常量迭代器类型,迭代器分类为random_access_iterator_tag;此外声明了一个_Map_pointer指向数据元素的指针的指针; 数据成员: _M_cur:指向当前node节点容器元素的当前指针; _M_first:指向当前的node节点容器第一个元素的指针; _M_last:指向当前的node节点容器最后一个元素尾部的指针; _M_node:指向node节点容器指针的指针(其值为对应的map缓冲区的槽, 而槽的值即为对应node节点缓冲区首地址); 成员函数: _S_buffer_size:静态函数,获取当前元素类型下的节点容器缓冲区大小(内部调用__deque_buf_size),如int时,则获得大小为512/4(即128个元素); 无参构造函数初始化所有成员为空,带参构造函数T*x, _Map_pointer y分别初始化_M_first和_M_node成员,_M_last为对应节点容器的尾元素指针(通过_S_buffer_size计算相对_M_first偏移值); 此外实现了赋值构造、取指针operator->、解引用操作符operator*返回当前_M_cur指针或数据元素值,operator-计算迭代器间距,重载的operator++、operator--、operator+=、operator-=等操作符 计算迭代器前向或后向偏移、状态;此外重载了各种条件判断操作符以及随机存取操作符operator[]; _M_set_node:重置当前迭代器节点位置; _Deque_iterator迭代器每个迭代器对象属于某个节点_M_node,属性有首、尾、当前位置的指针;从迭代器各操作看出,其deque容器结构为一个map的数组,保存指向各个节点位置的指针,各个节点为一片连续 内存缓冲区,而各个节点缓冲区相互间不一定连续(大多数下是不连续的),各个节点缓冲区下可容纳数据元素类型下大小均为_S_buffer_size获取的大小,map数组可伸缩且连续,而各个节点缓冲区是固定大小的, 此外因为如此不同于vector的连续内存,deque为间断性连续内存,故对容器直接取迭代器或取指针或operator[]取地址是不安全的; _Deque_alloc_base:deque分配基类模板;模板参数分别为数据类型T,分配器类型_Allocator,以及一个bool标识_IsStatic(用于区分是否为标准分配器或SGI分配器); 数据成员: _M_node_allocator:节点分配器对象; _M_map_allocator:map分配器对象(不同于map容器,其可认为是槽); _M_map:保存节点容器指针的缓冲区对象; _M_map_size:节点容器指针的缓冲区对象大小; 成员函数: 构造函数:分配器引用allocator_type类型以初始化_M_node_allocator、_M_map_allocator; get_allocator:获取节点分配器对象_M_node_allocator; _M_allocate_node、_M_deallocate_node:分配、释放指导地址且由__deque_buf_size获取的大小的节点缓冲区; _M_allocate_map、_M_deallocate_map:分配、释放指导地址且大小为n个元素的map缓冲区; 此外还提供特化版本_Deque_alloc_base<_Tp, _Alloc, true>,该分配模板基类内部不再使用分配器对象,而是直接使用simple_alloc的静态成员函数进行分配管理; _Deque_base:deque基类,继承于_Deque_alloc_base,其基类的模板参数_IsStatic则通过_Alloc_traits萃取获得的_S_instanceless来初始化; 数据成员: _M_start:容器数据起始迭代器对象; _M_finish:容器数据尾部迭代器对象; 事实上deque容器的访问、操作基本上均通过此两个迭代器实现,且最开始时_M_start和_M_finish均指向map缓冲区中间位置所对应的缓冲区(便于前向和后向插入数据时候减少重分配和调整map缓冲区的可能); 成员函数: 构造函数:重载版本,其中一个版本通过分配器类型以及map元素个数初始化,另一个则只是初始化基类分配器; 析构函数:调用_M_destroy_nodes、_M_deallocate_map销毁map缓冲区以及map缓冲区各指向的node节点缓冲区; _M_initialize_map:初始化大小为num元素个数的map缓冲区(内部实际操作为:计算元素个数下的需要的节点缓冲区数__num_nodes,num_nodes=max((size_t) _S_initial_map_size, __num_nodes + 2) 重新计算实际的节点缓冲区数,调用_M_allocate_map、_M_create_nodes分配map缓冲区大小以及从map中间位置处开始分配个数为__num_nodes的node节点缓冲区并调整_M_start、_M_finish迭代器位置); 实际上意味着当deque为空时,其至少也会分配_S_initial_map_size(8)元素类型下的map缓冲区大小以及一个node节点缓冲区; _M_create_nodes:创建map给定范围内的个数的node节点缓冲区(_M_allocate_node分配); _M_destroy_nodes:销毁map给定范围内的个数的node节点缓冲区(_M_deallocate_node分配); deque:序列容器deque模板类,保护继承于_Deque_base,模板参数中的分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc (malloc_alloc(即__malloc_alloc_template<0>)或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器; 此外重声明常规类型,迭代器和常迭代器类型,不同于vector的迭代器方式,vector迭代器不需要专门实现迭代器类,而是直接使用指针作为迭代器类型使用; get_allocator:重写基类函数,获取基类的分配器对象; 构造函数:多种重载版本的构造函数,包括提供分配的allocator_type参数、提供初始化元素个数n并以给定值value初始化或使用默认初始化值、提供数据元素首尾地址、 (内部调用_M_fill_initialize填充缓冲区元素); 析构函数:内部调用destroy销毁(调用析构函数)(不是释放)指定_M_start、_M_finish迭代器范围内存空间元素; _S_buffer_size:静态成员函数,获取node节点缓冲区大小(事实上调用的是__deque_buf_size); begin:获取迭代器首部(返回_M_start); end:获取尾迭代器(返回_M_finish); rbegin、rend:以const_reverse_iterator或reverse_iterator分别初始化构造_M_finish、_M_start并返回反转迭代器对象; operator[]:获取指定索引n下的数据元素值(返回_M_start[n]); at:功能同operator[],其会间接调用operator[],不过增加了边界检查,超过容器容量则抛出range_error异常; front:获取容器首元素的引用值(返回*_M_start); back:获取容器尾元素的引用值(返回_M_finish中cur的前一个迭代器位置的引用值); size:获取容器元素个数(返回_M_finish - _M_start); max_size:获取容器最大容量大小(返回size_type(-1),为何不是size_type(-1)/sizeof(T)?,不同编译器实现不一样); empty:判断容器是否为空(_M_finish == _M_start); _M_fill_initialize:填充迭代器_M_start至_M_finish的_M_cur范围的初始化某个值,内部循环调用uninitialized_fill实现; _M_initialize_dispatch:初始化分派分配,重载版本,其主要用在迭代器范围初始化deque容器,通过输入迭代器确定数据元素类型是否为_Is_integer,以分派初始化函数;若为_Is_integer则 调用_M_initialize_map、_M_fill_initialize分别分配map缓冲区和初始化填充值,否则调用_M_range_initialize再次进入分派; _M_range_initialize:提供两个重载版本的范围初始化操作,类似于vector中为提高某些拷贝处理等效率对输入迭代器(或其子类迭代器)和前向迭代器分别处理; 输入迭代器版本:先调用_M_initialize_map(0)预先分配初始化map缓冲区以及一个node缓冲区,再迭代器循环调用push_back初始化构造初始化值(有可能后期push_back导致再次申请node缓冲区); 前向迭代器版本:先调用distance获取输入迭代器范围大小n,再调用_M_initialize_map(n)分配足够的map缓冲区以及相应node缓冲区,此后尽可能地按照node节点缓冲区大小依次拷贝初始化; operator=:若源容器大小小于目标容器,则直接调用copy拷贝至目标容器,此后再调用erase移除其后多余的元素即可;否则则将源容器拷贝copy与目标容器大小的数据元素到目标容器,此后再 目标容器后insert插入源容器中剩下的数据元素(与vector不同在于不会释放早期的缓冲区而重新申请分配); swap:交换容器操作,实际上只需要交换内部迭代器_M_start和_M_finish、map缓冲区缓冲区指针对象、缓冲区大小; assign:分配函数,提供两个重载版本,一个版本参数为size_type n, const T& val表示分配n个值为val的元素容器(调用_M_fill_assign填充分配),另一版本参数为一个输入迭代器范围(调用_M_assign_dispatch 分配); _M_fill_assign:填充n个元素值为val的分配操作(若n大于size,则调用fill直接填充分配原容器内数据、再调用insert从未迭代器插入剩余个数的值,否则调用erase移除容器内部除前n个元素的后所有元素,并调用 fill填充分配源容器内数据即可; _M_assign_dispatch;分配分派函数,内部根据数据元素类型调用不同重载版本的_M_assign_aux;目前分为输入迭代器类型(或其子类类型)和前向迭代器类型; _M_assign_aux: 前向迭代器输入类型:循环迭代器直接赋值覆盖已有容器元素,若原容器有多余其他元素则调用erase移除,若还有剩余的输入迭代器数据则调用insert在原容器后插入; 前向迭代器输入类型:先调用distance获取输入迭代器范围len,若len大于容器原有的容量size,则调用copy拷贝size个大小的数据至原容器,对于剩余的输入迭代器数据则调用insert 在原容器尾迭代器处插入;若len小于size,则调用直接调用copy拷贝输入迭代器数据至原容器并调用erase移除原容器多余的元素即可; push_back:在容器尾部追加数据元素,内部判断是否需重新分配节点node缓冲区,若不需要则直接在_M_finish._M_cur位置处调用construct构造赋值即可,否则调用_M_push_back_aux追加数据; _M_push_back_aux:内部调用_M_reserve_map_at_back预先调整map缓冲区并调用_M_allocate_node申请节点node缓冲区,此后在_M_finish._M_cur位置处调用construct构造赋值并调整_M_finish迭代器; push_front:在容器首部追加数据元素,内部判断是否需重新分配节点node缓冲区,若不需要则直接在_M_start._M_cur前一位置处调用construct构造赋值即可,否则调用_M_push_front_aux推入数据; _M_push_front_aux:同_M_push_back_aux,只是调整map缓冲区并调用_M_allocate_node申请节点node缓冲区后,先调整_M_start迭代器后在_M_start._M_cur位置处调用construct构造赋值; _M_reallocate_map:重新调整map缓冲区,参数__add_at_front为是否在前方添加,该参数作用对于_M_start位置确定,当为前方添加数据元素时,其采取的策略为尽可能为前方提供多一点儿缓冲区而 对于后方添加数据时则从map缓冲区中部开始,其实后方添加和前方添加的可能性都比较大的,都无伤大雅; pop_back、pop_front:移除容器中首元素、尾元素;同样需要对node节点缓冲区判断处理,若迭代器在当前node节点缓冲区中,则直接调用destroy销毁当前迭代器cur位置的值并调整当前迭代器,否则 调用_M_pop_back_aux或_M_pop_front_aux处理下一个node节点缓冲区中的首元素或尾元素; _M_pop_back_aux:内部调用_M_deallocate_node销毁当前_M_finish指向的节点缓冲区并调整_M_finish迭代器,此后调用destroy销毁_M_finish._M_cur位置处的数据元素; _M_pop_front_aux:同_M_pop_back_aux类似,只是处理_M_start迭代器; insert:提供多种重载的插值版本; 在指定迭代器前插入数据元素x并返回插入位置时的迭代器: 内部判断插入迭代器position._M_cur位置是否为容器的首部或尾部,若是则直接调用push_front或push_back即可,否则调用_M_insert_aux插入; _M_insert_aux:针对插入位置靠近前方还是后方来处理,当靠近前方时则先取首元素并调用push_front插入该值,此后将后面迭代器位置至插入点迭代器位置范围数据调用copy前向拷贝至新的 首元素后的位置处;若靠近后方,则取尾元素并调用push_back插入该值,此后调用copy_backward将插入点至旧尾迭代器数据后向拷贝至插入点迭代器位置处,此后均调用直接给插入点迭代器 位置处覆盖赋值即可; 在指定迭代器前插入迭代器范围内的所有元素: 内部调用_M_insert_dispatch模板函数分派插入,当数据类型为_Is_integer时,则调用_M_fill_insert填充插入,否则调用insert模板函数插入; insert:插入模板函数; 当为输入迭代器时则构造插入迭代器inserter并调用copy实现拷贝插入; 当为前向迭代器时先调用distance获取迭代器参数范围,此时若插入位置为容器首部时,则调用_M_reserve_elements_at_front调整可能的map缓冲以及node节点缓冲区并调用uninitialized_copy 初始化拷贝至调整后的缓冲区区域;同样地若插入位置位于容器尾部时则调用_M_reserve_elements_at_back调整可能的map和node节点缓冲区并调用uninitialized_copy初始化拷贝;对于容器内部 某位置的插入则调用_M_insert_aux;该_M_insert_aux为迭代器范围的重载版本;其类似于vector中的_M_range_insert也是为了尽可能提高插入效率;不过_M_insert_aux更为复杂一点儿,分为多个 情况;1.插入点靠前时 2.插入点靠后时;此外这两种情况均还有插入数据比迭代器前方或后方数据长度长或短两种情况;即共2*2(4)种情形; 在指定迭代器前插入n个数据元素x:调用_M_fill_insert插入n个数据元素x的版本; resize:重载版本,提供设置容器大小的操作,若容器大小小于或等于给定大小,则调用insert尾部插入值X或默认值;若容器大小大于给定大小时则调用erase移除多余的容器元素即可; erase:若移除迭代器位置靠前则分别调用copy_backward、pop_front后向拷贝容器首迭代器至指定位置前范围的数据向后移动拷贝一个元素位并移除首元素即可; 若移除迭代器位置靠后则调用copy、pop_back拷贝容器指定位置后至尾部迭代器范围的数据向前移动拷贝一个元素位并移除尾部元素即可; clear:清空容器元素并销毁各node节点缓冲区,当销毁_M_start和_M_finish间的所有node节点缓冲区后,若两者迭代器仍处在不同node节点上,则分别调用destroy销毁各自节点元素,此后再调用_M_deallocate_node 销毁_M_finish所在的node节点缓冲区;若在同一个node节点时,则直接调用destroy销毁_M_start._M_cur, _M_finish._M_cur间的元素即可;不论怎样始终保留一个node节点缓冲区; 重载了多种条件比较运算符, deque若采用默认带内存池的版本分配器时,若超过128字节将直接采用malloc、free管理内存,一般情况下需求内存空间比较大的情况下时分配器意义不大基本上是直接采用malloc、free管理,容器内部缓冲区 结构不同于vector,vector的缓冲区一定是连续的,而deque则是预先分配具有8个或至少(所需节点缓冲区/节点缓冲区大小+2)个的槽的map的缓冲区,该缓冲区可根据需要会动态调整大小,此外每个槽对应一个node节点缓冲区,每个node节点缓冲区大小并不固定(至多512字节 或超过512字节大小的一个当前类型大小而已),此外deque为空时始终仍有一个node节点缓冲区可用而vector则没有可用缓冲区或者调用clear前的可用缓冲区大小,在内存占用方面:deque相对于vector只是多了一个map缓冲区而已, 主要是用来保存指向各个node节点缓冲区的首地址的槽;

 

posted @ 2019-10-10 12:30  浩月星空  阅读(237)  评论(0编辑  收藏  举报