SGI-STL简记-序列容器(bit_vector)

Posted haomiao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SGI-STL简记-序列容器(bit_vector)相关的知识,希望对你有一定的参考价值。

stl_bvector.h :
    bit_vector 重声明为vector<bool, alloc>即使用alloc作为内存分配器,其为非模板类,__BVECTOR也即vector<bool, _Alloc>为模板类,若使bit_vector重声明为__BVECTOR则也可为模板类;
    相比vector,其可保持一个位一个元素而不是至少一个字节一个元素;此外基本的函数和vector相同,还有其他额外的提供的接口实现;
    bit_vector一般被认为是非模板类;
    
    _Bit_reference:位引用类,作为位迭代器类和bit_vector类中的引用类型;
    数据成员,均为unsigned int类型或指针类型(后面为了便于分析,暂且定为4字节大小):
        _M_p:指向缓冲区中存储该位码值所在的地址;
        _M_mask:当前位掩码值(事实上目前一般情况下该值始终为仅含一个1其余均为0的二进制形式下的值),也即_M_p所指向的4字节位码中相应掩码值中为1的位置即为当前引用类表示的位值;
    成员函数:
        构造函数初始化位码值地址和掩码值;
        operator bool:重载bool转换,内部实现为将位码值和掩码值按照位取与的结果;
        operator=:重载赋值拷贝运算符,内部实现为为拷贝参数值为true,则调整位码值为当前位码值与掩码值按位或的结果,否则为当前位码值与掩码值取反后再与其求与的结果;
        事实上也就是将位码值所在的缓冲区下所对应掩码值位置的值修改为1或者是0;
        operator==:重载比较相等,均位引用对象以转化为bool后比较;
        operator<:仅当当前类对象为false且参数对象为true时为ture,否则均为false;
        flip:调整位码值为当前位码值与掩码值按位异或的结果
    一个工具函数:swap交换两个位引用对象,此外__WORD_BIT为字长或32或64,依赖于机器;    
    
    _Bit_iterator_base:位迭代器模板基类,继承于random_access_iterator<bool, ptrdiff_t>,这样就具有随机访问方式以及各重声明的常规类型;
    数据成员:
        _M_p:当前迭代器所指向的缓冲区存储数据节点(字长大小)的地址;
        _M_offset:位码偏移量,即当前迭代器所指代的位码值位置索引偏移(一般值范围为0~31或0~63,依据机器字大小决定),该值与_M_mask区别就是,_M_mask = 1 << _M_offset;
    成员函数:    
        构造函数初始化_M_p和_M_offset;
        _M_bump_up:迭代器后移(上提);若当前位码偏移量_M_offset已达到了字长,则调整_M_p指向下一个字长的位置且设置偏移量值为0,否则位码偏移量自加1即可;
        _M_bump_down:迭代器前移(下拉):若当前位码偏移量为0,则调整_M_p指向上一个字长的位置且设置偏移量值为字长大小-1,否则位码偏移量自减1即可;
        _M_incr:迭代器后移或前移i位;计算调整_M_p和_M_offset;
        此外还有多个重载的比较运算符,其中operator==则是当所指向地址和位码偏移量均相等时才相等;operator<则是所指向地址小于或者当所指向地址相等而位码偏移量小于时,
        其任意一条件满足则为true,否则为false;
    operator-:计算两个迭代器间的位码差值;
        
    _Bit_iterator:位迭代器类,继承于_Bit_iterator_base;内部通过_Bit_reference重声明其引用类型、指针类型;    
        构造函数初始化基类_M_p和_M_offset;
        重载了解引用operator*操作符,返回对_Bit_reference位引用类对象的封装(_Bit_reference(_M_p,1<<_M_offset));
        重载了自增operator++、operator++(int)以及自减operator--operator--(int):内部调用基类的_M_bump_up或_M_bump_down实现;
        重载了operator+=、operator-=、operator+、operator-(返回迭代器对象)以及operator[]操作符(返回_Bit_reference位引用对象);内部调用基类的_M_incr实现;
    
    _Bit_const_iterator    :常位迭代器类,其他实现同_Bit_iterator且构造函数多了一个拷贝构造函数允许从其他位迭代器对象构造;
    
    _Bvector_alloc_base:bit_vector分配基类模板;模板参数分别为分配器类型_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_bit_alloc:通过分配器对象_M_data_allocator分配大小为(n + __WORD_BIT - 1)/__WORD_BIT)个的bool类型大小内存空间,保证按字对齐,即至少会分配一个字的大小;
        _M_deallocate:释放_M_start指针地址且大小为_M_end_of_storage - _M_start._M_p的内存空间;
    此外还提供特化版本_Bvector_alloc_base<_Tp, _Allocator, true>,该分配模板基类内部不再使用分配器对象,而是直接使用simple_alloc的静态成员函数进行分配管理;
    
    _Bvector_base:    bit_vector基类模板,继承于_Bvector_alloc_base,内部重声明了基类类型、分配器类型;
        构造函数初始化基类分配器;
        析构函数调用基类的_M_deallocate释放申请的内存缓存空间;
        
    vector<bool, _Alloc>:继承于_Bvector_base<_Alloc>,该模板参数_Alloc可由用户外部提供,也可以使用内置的分配器,在typedef vector<bool, alloc>bit_vector时,此bit_vector默认使用的分配器为alloc;
    此外其重声明了常规类型以及由位引用作为引用和指针类型,迭代器和常迭代器由_Bit_iterator、_Bit_const_iterator重声明,还有反向迭代器包装类型;
        get_allocator:重写基类函数,获取基类的分配器对象;
        构造函数:多种重载版本的构造函数,包括提供分配的allocator_type参数、提供初始化元素个数n并以给定值value初始化或使用默认初始化值、提供数据元素迭代器范围、
        (内部调用_M_initialize、fill或者调用_M_initialize_dispatch分配_M_initialize_range初始化区间、申请缓冲区并填充缓冲区元素);
        拷贝构造函数:内部调用_M_initialize初始化缓冲区并调用copy拷贝数据至缓冲区,之所以可以使用fill或copy是因为bool或位码为内置POD类型不用初始化构造;此外当val为true时,
        填充值为~0,即二进制的全1;
        _M_initialize:内部调用_M_bit_alloc分配足够容纳n个位码的字长缓冲区,_M_end_of_storage保存申请到的缓冲区结束位置,_M_start保存缓冲区首地址以及初始化内部偏移量为0,
        此外_M_finish为保存有效数据后的尾部迭代器;
        _M_initialize_dispatch:分配初始化模板,依据输入迭代器类型调用不同的实现,当为_Is_integer时则调用_M_initialize、fill即可,否则调用_M_initialize_range初始化;
        _M_initialize_range:初始化范围,为重载模板函数;
            当输入参数为输入迭代器或子类迭代器时,预先使首尾迭代器、缓冲区结束位置为空,此后遍历输入迭代器范围依次调用push_back插入各数据位元素;
            当输入参数为前向对象迭代器时,内部先调用distance获取输入迭代器范围长度,此后调用_M_initialize申请缓冲区并调用copy直接拷贝数据到缓冲区中;
        其他与vector类似的接口功能类似,需要注意的是vector返回的引用可以取地址安全使用,而bit_vector返回的引用为位引用对象不可直接取地址使用,除非知道内存数据布局方式;
        以下对新增加的接口和特殊处理的接口分析;
        flip:位码开关,即该接口可将缓冲区中的位码若为0则变为1,若为1则变为0;内部采用for循环处理所有以字为单位直接取反达到各个位取反的目的;
        capacity:获取容器可容纳位码的容量大小(返回const_iterator(_M_end_of_storage, 0) - begin()的差值);
        erase:重载版本的移除指定位置的元素或迭代器范围的元素,其实现方式比较简单,直接调用copy移动拷贝并调整_M_finish位置即可;
        _M_insert_aux:在指定迭代器位置处插入指定val;若插入时后面还有多余缓冲区未被使用,则调用copy_backward后向拷贝数据移动一个位码,此后直接对插入位置赋值并调整_M_finish
        即可,否则若当前容器容量为0则调用_M_bit_alloc分配一个字长的缓冲区或者为当前容器容量的2倍空间的缓冲区,此后调用copy拷贝原容器缓冲区数据至新缓冲区,插入点位置则直接赋值,
        而对于原容器余下的插入点位置后的数据再次调用copy拷贝,最后调用_M_deallocate释放原容器缓冲区并调整_M_end_of_storage、_M_start、_M_finish位置;
        可能重新分配内存的操作:insert、push_back、赋值拷贝、reserve、resize;而earse、pop_back、clear等则不会重分配内存;
        
    bit_vector因其类似于vector,部分功能和接口基本一样,此外其实际分配到的也是内存为连续的,但直接取迭代器或取指针或operator[]取地址是不可以直接使用的,因为返回的实际上为一个
    称之为位引用的对象,其内部包含了多个位的值,若结合掩码值也可以获取到正确的相应位的值,此外若使用者对内存布局了解的话则还可以获取到其他索引位的位值;
    另外bit_vector采用alloc默认带内存池的版本分配器时的确很多时候还是可以减少内存碎片和频繁申请的,当然若超过128字节将直接采用malloc、free管理内存;
    相对vector,更节省内存占用,因其一个元素只需要一个bit即可,即可节省约8倍的空间占用,此外因引入了位引用导致部分接口实现效率要差一点儿;
    bit_vector的申请内存布局格式为(以32位字长为例):
        31 30 29 28 .... 2 1 0 | 63 62 61 .... 34 33 32 | 95 .......     
        对应数字表示容器索引位置下其在内存中的所在位置,字长位置的首地址并不是容器首元素的地址;
        如bit_vector V(4);
        V[0] = true;
        V[1] = true;
        V[2] = false;
        V[3] = true;
        二进制布局为:m_Ptr: 0000000....1011  对应十进制为11;当取V[3]时即(*m_ptr)&0000...1000,取V[1]时即(*m_ptr)&0000...0010

 

以上是关于SGI-STL简记-序列容器(bit_vector)的主要内容,如果未能解决你的问题,请参考以下文章

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

SGI-STL简记-序列容器(slist)

SGI-STL简记-序列容器(list)

SGI-STL简记-适配器(容器迭代器仿函数)

SGI-STL简记-工具杂项解析

SGI-STL简记-迭代器解析