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

vector:
  相对标准库中的array,其空间大小是动态的,随着元素的增加,其内部对空间大小的控制及重新配置时的数据移动策略,比较灵活的利用空间;而array为固定的静态空间,一旦配置后就不再改变,
  此外array在局部栈上也不建议申请较大的内存,因其内部实现为固定大小的数组,不同于vector在堆上的动态维护;
  vector的空间配置采取预先多分配原则,以减少空间配置时的速度成本,即可能申请的实际空间大小容量会比较存储实际内容多或相同;另外因内部成员变量_M_start作为vector的内存模型时的第一个
 成员变量,其地址即为vector的地址,故也可以使用取vector的地址作为元素的首地址;
  vector的空间配置策略:当增加元素时,若存储空间不足则申请以max(原空间内容大小, 新插入元素个数)的两倍再重新申请,并将原内容拷贝到新申请的内存块中,新添加的元素放置在对应的位置并调用其构造函数,而后释放原内存块;
任何导致内存空间重分配的操作,都会导致早期获取的迭代器失效;
  vector操作时间复杂度:
    首部或中部插入元素,复杂度O(n); 尾部插入时O(1);
    首部或中部移除元素,复杂度O(n);尾部移除时O(1);
    索引查找复杂度O(1),值遍历查找复杂度O(n);

stl_vector.h : vector:可随机访问元素的序列容器,从后插入或删除在常量时间内完成,从首部或中间则需线性时间内完成; _Vector_alloc_base:vector分配基类模板;模板参数分别为数据类型T,分配器类型_Allocator,以及一个bool标识_IsStatic(用于区分是否为标准分配器或SGI分配器); 数据成员: _M_data_allocator:分配器对象; _M_start:保存申请的缓冲区首地址(等同于容器元素的首地址); _M_finish:保存容器内容长度时的尾地址; _M_end_of_storage:保存申请的缓冲区尾地址; 成员函数: 构造函数:分配器引用allocator_type类型以初始化_M_data_allocator; get_allocator:获取分配器对象_M_data_allocator; _M_allocate:通过分配器对象_M_data_allocator分配大小为n个的元素类型大小内存空间; _M_deallocate:释放指定数据元素类型指针地址大小为n个数据元素类型大小的内存空间; 此外还提供特化版本_Vector_alloc_base
<_Tp, _Allocator, true>,该分配模板基类内部不再使用分配器对象,而是直接使用simple_alloc的静态成员函数进行分配管理; _Vector_base:vector基类,继承于_Vector_alloc_base,其基类的模板参数_IsStatic则通过_Alloc_traits萃取获得的_S_instanceless来初始化; 构造函数:重载版本,一种通过分配器类型对象初始化;另一种还增加了参数n,内部调用_M_allocate以预先分配n字节的缓冲区并调整_M_start、_M_finish、_M_end_of_storage值; 析构函数:调用_M_deallocate释放当前缓冲区; vector:序列容器vector模板类,保护继承于_Vector_base,模板参数中的分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc (malloc_alloc(即__malloc_alloc_template<0>)或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器; 此外重声明常规类型以及迭代器类型和常量迭代器类型(vector中迭代器类型与指针类型相同,不同于其他容器(如:list、deque等),其他容器需要单独提供相应迭代器); get_allocator:重写基类函数,获取基类的分配器对象; 构造函数:多种重载版本的构造函数,包括提供分配的allocator_type参数、提供初始化元素个数n并以给定值value初始化或使用默认初始化值、提供数据元素首尾地址、 (内部调用uninitialized_fill_n填充缓冲区元素)并调整__M_finish值; 拷贝构造函数:同上初始化过程; 析构函数:内部调用destroy销毁(不是释放)指定范围_M_start、_M_finish内存空间元素; begin:获取迭代器首部(返回_M_start); end:获取迭代器尾部(返回_M_finish); rbegin、rend:借助begin和end以初始化构造并返回反转迭代器对象; size:获取vector容量,也即迭代器范围(end()-begin()); max_size:获取可容纳最大元素容量大小(size_type(-1)/sizeof(T)); capacity:获取当前可容纳元素容量(size_type(_M_end_of_storage-begin()))(实际上即为当前容器已分配的内存空间大小); empty:判断容器是否为空(begin()==end()); operator[]:返回指定索引下引用元素(*(begin()+n)); at:功能同operator[],其会间接调用operator[],不过增加了边界检查,超过容器容量则抛出range_error异常; _M_allocate_and_copy:申请n字节大小内存空间并拷贝迭代器范围的原始元素数据至该空间,返回申请的内存空间首地址(迭代器); reserve:预订至少大于当前可容纳元素容量的n字节大小的内存空间(若小于或等于capacity则不做任何处理,否则调用_M_allocate_and_copy重申请内存空间,此后销毁原容器元素和内存空间并重新调整 容器迭代器以及容量指针_M_end_of_storage为新申请的内存空间位置),故该操作可能导致大量的销毁和重新申请内存; assign:分配函数,提供两个重载版本,一个版本参数为size_type n, const T& val表示分配n个值为val的元素容器(调用_M_fill_assign填充分配),另一版本参数为一个输入迭代器范围(调用_M_assign_dispatch 分配); _M_fill_assign:填充n个元素值为val的分配操作(若n大于capacity则需重新申请内存空间,不过其实现是通过重新申请n个元素值为val的vector对象并通过swap直接交换(事实上只是简单地交换了迭代器指针对象而已) ;若n小于capacity且大于size,则调用fill直接填充、uninitialized_fill_n初始化迭代器尾部后剩余的值并调整_M_finish尾迭代器即可;若n小于size,则先调用fill_n填充迭代器n个元素,调用erase销毁多余的 内存空间); _M_assign_dispatch:分配分派模板,若迭代器对应元素类型为整型_Is_integer则调用_M_assign_dispatch模板参数为__true_type的版本(内部调用_M_fill_assign填充分配);否则调用_M_assign_aux分配,事实上该 函数也有两个重载版本,针对输入迭代器或其子类类型和前向迭代器,之所以如此,因后者迭代器实现方式可更快速实现而不是输入迭代器的方式(当然后者采用前者的方式实现也可以,不过for遍历获取长度相对比较慢, 如为随机迭代器等时会损失高效性(随机迭代器获取长度时可直接相减即可得到)); _M_assign_aux:输入迭代器类型版本,通过直接将传参迭代器元素覆盖已存在的容器迭代位置容器元素,若输入参数迭代器元素比容器已有的少,则移除原容器多余元素;若输入参数迭代器还有多余则调用insert插入 剩余的元素至容器中; _M_assign_aux:前向迭代器类型版本,先调用distance获取前向迭代器范围len,若len大于capacity则需重新申请内存空间,不过其实现是通过重新申请n个元素值为val的vector对象并通过swap直接交换(事实上只是简单地交换了迭代器指针对象而已) ;若len小于capacity且大于size,则调用advance获取first前向迭代器向前跳size时的迭代器mid,再调用copy拷贝first与mid迭代器范围数据至原容器、此后再调用uninitialized_copy初始化迭代器尾部后剩余的值并调整_M_finish尾迭代器; 若len小于size,则调用copy拷贝first至last迭代器数据至原容器,再调用destroy销毁剩余多余原容器数据元素并调整_M_finish; front、back:获取容器首元素和尾元素(*begin()、*(end() - 1),若容器为空会出现运行时异常); push_back:容器尾部推入数据元素,若推入前还有足够缓冲区则直接在尾部迭代器_M_finish处调用construct初始化构造并调整_M_finish即可,否则调用_M_insert_aux在尾迭代器前插入数据元素并调整_M_finish; _M_insert_aux:在指定迭代器前插入数据元素x;若插入前还有足够缓冲区则调整尾迭代器+1并调用copy_backward后向拷贝元素数据,此后在插入位置直接覆盖原始数据元素即可;若不足则需要申请足够的内存空间,其策略为:若原容器容量为0,则 申请1个元素空间即可,否则申请原始空间的2倍内存空间;接着调用uninitialized_copy拷贝原容器迭代器范围为首迭代器至插入迭代器点的数据元素至新内存空间中,此后在新内存空间的尾迭代器处调用construct初始化插入元素,再将剩下原容器的 剩余部分的数据元素调用uninitialized_copy依次拷贝到后面,最后调用destroy、_M_deallocate释放容器所有元素和内存空间并调整原容器的迭代器为新的内存空间地址范围和容量即可; swap:交换两个vector容器对象(事实上内部只是简单地交换了迭代器指针而已); insert:提供三个重载版本; 在指定迭代器前插入数据元素x并返回插入位置时的迭代器: 若插入前还有足够缓冲区且在尾迭代器前插入则直接调用construct在尾迭代器_M_finish处初始化值x并调整_M_finish即可;否则调用_M_insert_aux实现具体的插入操作; 在指定迭代器前插入迭代器范围内的所有元素: 其调用_M_insert_dispatch并根据数据元素类型确定其参数_Is_integer是否为整型,若为整型则再调用_M_fill_insert进行插入填充,否则调用_M_range_insert插入,然而_M_range_insert为模板函数, 具体调用哪个_M_range_insert也是根据数据元素迭代器类型,当为输入迭代器时则遍历输入迭代器循环调用insert插入数据(在遍历插入过程中需调整插入点迭代器位置),当为前向迭代器时采用的策略 为:先调用distance获取前向迭代器范围len,此时可能的出现容器可用剩余内存不足的情况; 1. 若len小于剩余可用内存时则又分为两种情况(之所以有两种情况是为了尽可能提高插入效率(尤其是数据元素为整型等内置类型时)),
              此外,copy_backward操作相对开销会比较大一些,尽可能较少该后向拷贝操作,而uninitialized_copy则内置类型时可能会调用memmove效率会很高;
1). 插入点到尾迭代器长度比插入迭代器范围大: 调用uninitialized_copy拷贝构造尾迭代器前n个元素时范围内数据至尾迭代器位置处并调整容器尾迭代器位置; 调用copy_backward将插入处迭代器至插入迭代器后未被拷贝的范围元素后向拷贝至原尾迭代器处; 调用copy将输入迭代器范围元素拷贝到插入点处; 2). 插入点到尾迭代器长度不比插入迭代器范围大: 获取尾迭代器至插入点迭代器的范围长度__elems_after; 调用advance获取输入迭代器首迭代器位置向后移动__elems_after时的位置mid; 调用uninitialized_copy拷贝构造mid至输入迭代器尾迭代器范围数据至容器尾迭代器位置并调整容器尾迭代器位置; 调用uninitialized_copy拷贝构造插入点迭代器至容器原尾迭代器范围数据至新容器迭代器位置处并调整容器尾迭代器位置; 调用copy将输入迭代器范围元素拷贝到插入点处; 2. 可用剩余内存不足时: 获取原容器大小并与输入迭代器范围长度中取最大值加上原容器大小作为新的将申请的容器缓冲区大小,申请新的缓冲区; 调用uninitialized_copy拷贝构造容器首迭代器至插入点迭代器范围数据至新的缓冲区首迭代器处并调整记录尾迭代器; 调用uninitialized_copy拷贝构造输入迭代器范围数据至新的缓冲区尾迭代器处并调整新的缓冲区尾部迭代器; 调用uninitialized_copy拷贝构造原容器插入点迭代器至原容器尾迭代器范围数据至新缓冲区尾迭代器处; 调用destroy、_M_deallocate销毁原容器元素和内存空间,调用容器各迭代器为新缓冲区的相应迭代器; 在指定迭代器前插入n个数据元素x: 调用_M_fill_insert填充插入,策略同_M_range_insert的前向迭代器类型的版本; pop_back:弹出容器尾部元素(前向移动尾迭代器并调用destroy销毁尾部元素); erase:两个重载版本,移除指定迭代器位置的元素版本(个人觉得有BUG,传入迭代器位置不能超过尾迭代器前一个否则会出现pop_back现象)调用copy直接将移除点迭代器后至尾迭代器拷贝至移除点前一个迭代器处; 前向移动尾迭代器并调用destroy销毁尾部元素返回移除点迭代器;移除迭代器范围版本类似; resize:重置容器大小,提供两个重载版本;带初始化参数值版本中若需求容器大小new_size小于原容器大小,则调用earse移除容器首迭代器后new_size开始的迭代器至尾迭代器范围的数据元素并调整尾迭代器位置; 否则调用insert在原容器尾迭代器处插入容器不足new_size大小的值为x的数据元素; 不带初始化参数值版本,通过数据元素类型的默认构造函数以调用resize带参参数值版本; clear:调用erase移除容器数据元素; 此外重载了多个不同的比较操作符以支持比较运算; 重载赋值操作符operator=:以支持赋值操作,内部也采用了尽可能加快效率的操作;
vector若采用默认带内存池的版本分配器时,若超过128字节将直接采用malloc、free管理内存,一般情况下vector需求内存空间比较大的情况下时内存池的意义将变得不大,尤其是某些vector成员函数的操作更是频繁申请、 销毁内存空间,故内存池的意义主要在于减少内存碎片和频繁申请小内存空间的功能;当然若使用者提供自定义内存分配器(内存池)也可以自主管理策略;此外因为vector实际分配到的内存为连续的,故可以直接取迭代器或 取指针或operator[]取地址也是安全的;

 

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