《STL源码剖析》——Vector

vector

vector的源码分为多个文件:vector、<<stl_vector.h>>

vector的底层实现是在<<stl_vector.h>>中

1 vector的基本架构

vector的基本架构如下图所示:

2 vector内的成员变量

在vector本身中,有三个成员变量:_M_start_M_finish_M_end_of_storage

_M_start对应的是vector的begin()(目前使用空间的头)

_M_finish对应的是vector的end()(目前使用空间的尾)

_M_end_of_storage对应的则是当前vector的容量(目前可用空间的尾,注意与_M_finish的区别)

3 vector的iterator

vector的iterator是__gnu_cxx::__normal_iterator<pointer, vector>类型的,__normal_iterator的主要声明如下:

    template<typename _Iterator, typename _Container>
    class __normal_iterator {
    protected:
      _Iterator _M_current;
    
      typedef iterator_traits<_Iterator>        __traits_type;
    
    public:
      typedef _Iterator                 iterator_type;
      typedef typename __traits_type::iterator_category iterator_category;
      typedef typename __traits_type::value_type    value_type;
      typedef typename __traits_type::difference_type   difference_type;
      typedef typename __traits_type::reference     reference;
      typedef typename __traits_type::pointer       pointer;
        
        // ...
    }

4 push_back与扩容

重点观察一下vector的push_back方法,其源码如下:

    void
    push_back(const value_type& __x) {
            if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
                _GLIBCXX_ASAN_ANNOTATE_GROW(1);
                _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
              ++this->_M_impl._M_finish;
                _GLIBCXX_ASAN_ANNOTATE_GREW(1);
            } else _M_realloc_insert(end(), __x);
    }

push_back函数分为以下步骤:

  1. 先检查当前是否还存在可用空间,即_M_finish是否等于_M_end_of_storage,如果成立,则跳到2,不成立跳到3
  2. 构造一个新的元素并插入vector中,并调整_M_finish指针
  3. 调用_M_realloc_insert,另寻他处,重新开辟空间,将原来的vector中的元素都拷贝到新的vector中,并将新元素也插入vector中,并销毁原来的vector以及其中的元素(步骤3开销很大)

注意:如果对vector的任何操作,导致了vector的重新分配行为,那么原有的iterator都会全部失效

5 insert与扩容

vector提供了很多种的insert,以其中一个为例:在指定的iterator之前插入n个初值为x的元素形式的insert

insert是可能触发vector的重新分配行为的

例子中insert的源码如下:

    template<typename _Tp, typename _Alloc>
    void
    vector<_Tp, _Alloc>::
    _M_fill_insert(iterator __position, size_type __n, const value_type& __x) {
            if (__n != 0) {
                if (size_type(this->_M_impl._M_end_of_storage   - this->_M_impl._M_finish) >= __n) {
        #if __cplusplus < 201103L
                value_type __x_copy = __x;
        #else
                _Temporary_value __tmp(this, __x);
                value_type& __x_copy = __tmp._M_val();
        #endif
                const size_type __elems_after = end() - __position;
                pointer __old_finish(this->_M_impl._M_finish);
                if (__elems_after > __n) {
                      _GLIBCXX_ASAN_ANNOTATE_GROW(__n);
                      std::__uninitialized_move_a(this->_M_impl._M_finish - __n, this->_M_impl._M_finish, this->_M_impl._M_finish, _M_get_Tp_allocator());
                      this->_M_impl._M_finish += __n;
                      _GLIBCXX_ASAN_ANNOTATE_GREW(__n);
                      _GLIBCXX_MOVE_BACKWARD3(__position.base(), __old_finish - __n, __old_finish);
                      std::fill(__position.base(), __position.base() + __n, __x_copy);
                    } else {
                      _GLIBCXX_ASAN_ANNOTATE_GROW(__n);
                      this->_M_impl._M_finish =
                    std::__uninitialized_fill_n_a(this->_M_impl._M_finish, __n - __elems_after, __x_copy, _M_get_Tp_allocator());
                      _GLIBCXX_ASAN_ANNOTATE_GREW(__n - __elems_after);
                      std::__uninitialized_move_a(__position.base(), __old_finish, this->_M_impl._M_finish, _M_get_Tp_allocator());
                      this->_M_impl._M_finish += __elems_after;
                      _GLIBCXX_ASAN_ANNOTATE_GREW(__elems_after);
                      std::fill(__position.base(), __old_finish, __x_copy);
                    }
          } else {
                const size_type __len = _M_check_len(__n, "vector::_M_fill_insert");
            const size_type __elems_before = __position - begin();
            pointer __new_start(this->_M_allocate(__len));
            pointer __new_finish(__new_start);
            __try {
                  // See _M_realloc_insert above.
                  std::__uninitialized_fill_n_a(__new_start + __elems_before, __n, __x, _M_get_Tp_allocator());
                  __new_finish = pointer();
                  __new_finish = std::__uninitialized_move_if_noexcept_a(this->_M_impl._M_start, __position.base(), __new_start, _M_get_Tp_allocator());
                  __new_finish += __n;
        
                  __new_finish = std::__uninitialized_move_if_noexcept_a(__position.base(), this->_M_impl._M_finish, __new_finish, _M_get_Tp_allocator());
                } __catch(...) {
                  if (!__new_finish)
                    std::_Destroy(__new_start + __elems_before, __new_start + __elems_before + __n, _M_get_Tp_allocator());
                  else
                    std::_Destroy(__new_start, __new_finish,
                      _M_get_Tp_allocator());
                      _M_deallocate(__new_start, __len);
                  __throw_exception_again;
                }
            std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
                _M_get_Tp_allocator());
            _GLIBCXX_ASAN_ANNOTATE_REINIT;
            _M_deallocate(this->_M_impl._M_start,
                this->_M_impl._M_end_of_storage
                - this->_M_impl._M_start);
            this->_M_impl._M_start = __new_start;
            this->_M_impl._M_finish = __new_finish;
            this->_M_impl._M_end_of_storage = __new_start + __len;
                }
            }
    }

这个重载的insert源码比较长,但是大致思路如下:

  1. if (__n != 0) 检查插入的元素个数n是否为0
  2. if (size_type(this->_M_impl._M_end_of_storage - this->_M_impl._M_finish) >= __n) 检查可用空间是否大于等于n,如果此条成立还需要区分新插入的元素个素n与安插点之后的元素个数__elems_after的大小情况

    2.1 n < __elems_after

    2.2 n ≥ __elems_after

  3. 如果第二条不成立,那么就需要对vector进行扩容,那么新vector的长度取决于max(旧长度 x 2,(旧长度 + n个新增元素) x 2)

    需要注意的是,新的vector不必是在旧的vector上的扩展,图中画在一起,只是为了方便

猜你喜欢

转载自www.cnblogs.com/xhb19960928/p/11839105.html