stl源码分析之vector

上篇简单介绍了gcc4.8提供的几种allocator的实现方法和作用,这是所有stl组件的基础,容器必须通过allocator申请分配内存和释放内存,至于底层是直接分配释放内存还是使用内存池等方法就不是组件需要考虑的事情。这篇文章开始分析gcc4.8 stl的容器源码实现。stl的容器分为序列式容器和关联式容器,前者包括vector,list,queue以及stack等常用数据结构,后者包含了map,set以及hash table等比较高级的结构,本文就从使用最广泛也是最基础的vector开始。

一、 vector的定义

vector继承自_Vector_base, _Vector_base包装了内存管理器_M_impl,下面是Vector_base的定义,由于代码过多,删除了一些不妨碍理解的源码:

template<typename _Tp, typename _Alloc>
    struct _Vector_base
    {
//指定特定数据类型的内存分配器
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
        rebind<_Tp>::other _Tp_alloc_type;
      typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
        pointer; 

      struct _Vector_impl
      : public _Tp_alloc_type
      {
       pointer _M_start;  //使用空间起始位置
        pointer _M_finish;  //使用空间结束位置
        pointer _M_end_of_storage;  //可使用空间结束位置
        _Vector_impl()
        : _Tp_alloc_type(), _M_start(0), _M_finish(0), _M_end_of_storage(0)
        { }
        _Vector_impl(_Tp_alloc_type const& __a)
        : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
        { }
        _Vector_impl(_Tp_alloc_type&& __a)
        : _Tp_alloc_type(std::move(__a)),
          _M_start(0), _M_finish(0), _M_end_of_storage(0)
        { }
//vector数据交换,只交换内存地址,不交换数据
        void _M_swap_data(_Vector_impl& __x)
        {
          std::swap(_M_start, __x._M_start);
          std::swap(_M_finish, __x._M_finish);
          std::swap(_M_end_of_storage, __x._M_end_of_storage);
        }
      };
 public:
      _Vector_base()
      : _M_impl() { }
      _Vector_base(const allocator_type& __a)
      : _M_impl(__a) { }
      _Vector_base(size_t __n)
      : _M_impl()
      { _M_create_storage(__n); }
      _Vector_base(size_t __n, const allocator_type& __a)
      : _M_impl(__a)
      { _M_create_storage(__n); }
    _Vector_base(_Vector_base&& __x)    //移动构造函数,只交换3个指针,不copy数据
      : _M_impl(std::move(__x._M_get_Tp_allocator()))
      { this->_M_impl._M_swap_data(__x._M_impl); }
//释放内存空间
      ~_Vector_base()
      { _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
                      - this->_M_impl._M_start); }
    public:
      _Vector_impl _M_impl;
      pointer
    //通过 _M_impl申请内存
      _M_allocate(size_t __n)
      { return __n != 0 ? _M_impl.allocate(__n) : 0; }
      void
   //通过 _M_impl释放内存
      _M_deallocate(pointer __p, size_t __n)
      {
        if (__p)
          _M_impl.deallocate(__p, __n);
      }
    private:
      void
      _M_create_storage(size_t __n)
      {
        this->_M_impl._M_start = this->_M_allocate(__n);
        this->_M_impl._M_finish = this->_M_impl._M_start;
        this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
      }
    };

Vector_base专门负责vector的内存管理,内部类_M_impl通过继承_Tp_alloc_type得到内存分配释放的功能,_M_allocate和_M_deallocate分别分配和释放vector所用内存,vector只需要负责元素构造和析构。

rebind函数的原型是:

template<typename _Tp1>  struct rebind
        { typedef allocator<_Tp1> other; };

用于得到特定类型的分配器,此处直接得到vector元素类型_Tp的分配器,其实并没有体现其作用,在list中我们将会看到它的技巧所在。

vector继承于vector_base,具体内容后面讲解

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
   {
…
}

默认使用 std::allocator内存分配器,即上篇文章介绍的new_allocator,简单封装了operator new和operator delete 。

类结构图如下,

二 、vector的迭代器

vector使用的是__normal_iterator迭代器,

typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator; 
typedef __gnu_cxx::__normal_iterator<const_pointer, vector> const_iterator;

__normal_iterator定义于stl_iterator.h,封装了vector元素的指针,

template<typename _Iterator, typename _Container>
    class __normal_iterator
    {
    protected:
      _Iterator _M_current;
      typedef iterator_traits<_Iterator>                __traits_type;
    public: 

      _GLIBCXX_CONSTEXPR __normal_iterator() : _M_current(_Iterator()) { }
      explicit
      __normal_iterator(const _Iterator& __i) : _M_current(__i) { }
//拷贝构造函数
      template<typename _Iter>
        __normal_iterator(const __normal_iterator<_Iter,
                          typename __enable_if<
               (std::__are_same<_Iter, typename _Container::pointer>::__value),
                      _Container>::__type>& __i)
        : _M_current(__i.base()) { }
      reference
      operator*() const
      { return *_M_current; }
      pointer
      operator->() const
      { return _M_current; }
 __normal_iterator&
      operator++()
      {
        ++_M_current;
        return *this;
      }
      __normal_iterator
      operator++(int)
      { return __normal_iterator(_M_current++); }
      __normal_iterator&
      operator--()
      {
        --_M_current;
        return *this;
      }
      __normal_iterator
      operator--(int)
      { return __normal_iterator(_M_current--); }
      reference
      operator[](const difference_type& __n) const
      { return _M_current[__n]; } 

      __normal_iterator&
      operator+=(const difference_type& __n)
      { _M_current += __n; return *this; }
      __normal_iterator
      operator+(const difference_type& __n) const
      { return __normal_iterator(_M_current + __n); }
 __normal_iterator&
      operator-=(const difference_type& __n)
      { _M_current -= __n; return *this; }
      __normal_iterator
      operator-(const difference_type& __n) const
      { return __normal_iterator(_M_current - __n); }
      const _Iterator&
      base() const
      { return _M_current; }
    };

_M_current是指向迭代器位置的指针,这是一个随机访问型指针,operator+和operator-等移动操作可以直接移动到目的地,非随机访问型指针只能一步步移动。

vector还会使用reverse_iterator,即逆序迭代器,顾名思义,其移动方向与普通迭代器相反

explicit reverse_iterator(iterator_type __x) : current(__x) { }
  reference  operator*() const
      {
        _Iterator __tmp = current;
        return *--__tmp;  //取前一个位置上的值
      }
   reverse_iterator& operator++()
      {
        --current;
        return *this;
      }
    reverse_iterator& operator--()
      {
        ++current;
        return *this;
      }

reverse_iterator的值并不在当前位置上,而是前一个位置上的值,++和--跟普通迭代器的移动方向正好相反。

三、 vector的数据存储

vector内存由_M_impl中的M_start,_M_finish,_M_end_of_storage三个指针管理,所有关于地址,容量大小等操作都需要用到这三个指针:

   iterator begin() _GLIBCXX_NOEXCEPT
      { return iterator(this->_M_impl._M_start); }
   iterator end() _GLIBCXX_NOEXCEPT
      { return iterator(this->_M_impl._M_finish); }
    reverse_iterator  rbegin() noexcept
      { return reverse_iterator(end()); }
    reverse_iterator rend() noexcept
      { return reverse_iterator(begin()); }
     size_type size() const _GLIBCXX_NOEXCEPT
      { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
    size_type capacity() const _GLIBCXX_NOEXCEPT
      { return size_type(this->_M_impl._M_end_of_storage
                         - this->_M_impl._M_start); }
   bool empty() const _GLIBCXX_NOEXCEPT
      { return begin() == end(); }

结构图如下:

_M_finish和_M_end_of_storage之间的空间没有数据,有时候这是一种浪费,c++11提供了一个很有用的函数shrink_to_fit(),将这段未使用空间释放,主要调用了下面代码,

_Tp(__make_move_if_noexcept_iterator(__c.begin()),
                __make_move_if_noexcept_iterator(__c.end()),
                __c.get_allocator()).swap(__c);

先把M_start,_M_finish之间的数据拷贝出来,再和原vector做一次swap,新的vector只包含M_start与_M_finish之间的元素,原vector空间被释放。

四、 vector的构造方式

vector提供了多种构造函数,

    vector() : _Base() { }  //使用默认内存分配器
    explicit vector(const allocator_type& __a)  //指定内存分配器
      : _Base(__a) { }
//初始化为n个__value值,如果没指定就使用该类型默认值
    explicit vector(size_type __n, const value_type& __value = value_type(),
             const allocator_type& __a = allocator_type())
      : _Base(__n, __a)
      { _M_fill_initialize(__n, __value); }
//拷贝构造函数
    vector(const vector& __x)
      : _Base(__x.size(),
        _Alloc_traits::_S_select_on_copy(__x._M_get_Tp_allocator()))
      { this->_M_impl._M_finish =
          std::__uninitialized_copy_a(__x.begin(), __x.end(),
                                      this->_M_impl._M_start,
                                      _M_get_Tp_allocator());
      }
//c++11的移动构造函数,获取__x的M_start,_M_finish,_M_end_of_storage,并不需要数据拷贝
  vector(vector&& ) noexcept
      : _Base(std::move(__x)) { }
//从list中拷贝数据,也是c++11才有的
 vector(initializer_list<value_type> __l,
             const allocator_type& __a = allocator_type())
      : _Base(__a)
      {
        _M_range_initialize(__l.begin(), __l.end(),
                            random_access_iterator_tag());
//支持vector使用两个迭代器范围内的值初始化,除了stl的迭代器,也可以是数组地址
    template<typename _InputIterator,
               typename = std::_RequireInputIter<_InputIterator>>
        vector(_InputIterator __first, _InputIterator __last,
               const allocator_type& __a = allocator_type())
        : _Base(__a)
        { _M_initialize_dispatch(__first, __last, __false_type()); }
//只析构所有元素,释放内存由vector_base完成
   ~vector() _GLIBCXX_NOEXCEPT
      { std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
                      _M_get_Tp_allocator()); }

五、 vector的元素操作

vector的元素操作方式很丰富,这里只讨论insert函数,insert函数在指定位置前插入元素,可能需要移动数据和分配空间。

typename vector<_Tp, _Alloc>::iterator
    vector<_Tp, _Alloc>::
    insert(iterator __position, const value_type& __x)
    {
      const size_type __n = __position – begin();
     //插入到最后一个位置,相当于push_back
      if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage
          && __position == end())
        {
          _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
          ++this->_M_impl._M_finish;
        }
      else
        {
            _M_insert_aux(__position, __x);
        }
      return iterator(this->_M_impl._M_start + __n);
    }

 template<typename _Tp, typename _Alloc>
    void
    vector<_Tp, _Alloc>::
    _M_insert_aux(iterator __position, const _Tp& __x)
    {
      if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
        { //内存空间足够
          _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                   _GLIBCXX_MOVE(*(this->_M_impl._M_finish
                                                   - 1)));
          ++this->_M_impl._M_finish;
       //__position后的元素依次向后移动一个位置
          _GLIBCXX_MOVE_BACKWARD3(__position.base(),
                                  this->_M_impl._M_finish - 2,
                                  this->_M_impl._M_finish – 1);
       //目标地址赋值
          *__position = _Tp(std::forward<_Args>(__args)...);
        }
      else
        {
         //内存加倍
          const size_type __len =
            _M_check_len(size_type(1), "vector::_M_insert_aux");
          const size_type __elems_before = __position - begin();
          pointer __new_start(this->_M_allocate(__len));
          pointer __new_finish(__new_start);
          __try
            {
       //给position位置赋值
        _Alloc_traits::construct(this->_M_impl,
                                       __new_start + __elems_before,
                                       std::forward<_Args>(__args)...);
                                       __x);
              __new_finish = 0;
      //拷贝position位置前元素
              __new_finish
                = std::__uninitialized_move_if_noexcept_a
                (this->_M_impl._M_start, __position.base(),
                 __new_start, _M_get_Tp_allocator());

              ++__new_finish;
           //拷贝position位置后元素
              __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)
                _Alloc_traits::destroy(this->_M_impl,
                                       __new_start + __elems_before);
              else
                std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
              _M_deallocate(__new_start, __len);
              __throw_exception_again;
            }

      //析构原vector所有元素
          std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
                        _M_get_Tp_allocator());
       //释放原vector内存空间
          _M_deallocate(this->_M_impl._M_start,
                        this->_M_impl._M_end_of_storage
?    this->_M_impl._M_start);
      //vector内存地址指向新空间
          this->_M_impl._M_start = __new_start;
          this->_M_impl._M_finish = __new_finish;
      this->_M_impl._M_end_of_storage = __new_start + __len;
        }
    }

//内存分配策略并不是简单的加倍,如果n小于当前size,内存加倍,否则内存增长n。
   size_type
      _M_check_len(size_type __n, const char* __s) const
      {
        if (max_size() - size() < __n)
          __throw_length_error(__N(__s));

        const size_type __len = size() + std::max(size(), __n);
        return (__len < size() || __len > max_size()) ? max_size() : __len;
      }

stl源码分析之vector

时间: 2024-10-26 14:29:55

stl源码分析之vector的相关文章

STL源码分析 # vector #

下面是一个使用vector的demo: #include <iostream> #include <vector> using namespace std; int main() { /* ** To create a vector which contain 10 elements and the value ** of each element is 1 */ vector<int> vec(10, 1); vector<int>::iterator i

stl源码分析之hash table

本文主要分析g++ stl中哈希表的实现方法.stl中,除了以红黑树为底层存储结构的map和set,还有用哈希表实现的hash_map和hash_set.map和set的查询时间是对数级的,而hash_map和hash_set更快,可以达到常数级,不过哈希表需要更多内存空间,属于以空间换时间的用法,而且选择一个好的哈希函数也不那么容易. 一. 哈希表基本概念 哈希表,又名散列表,是根据关键字直接访问内存的数据结构.通过哈希函数,将键值映射转换成数组中的位置,就可以在O(1)的时间内访问到数据.举

stl源码分析之list

本文主要分析gcc4.8版本的stl list的源码实现,与vector的线性空间结构不同,list的节点是任意分散的,节点之间通过指针连接,好处是在任何位置插入删除元素都只需要常数时间,缺点是不能随机访问,查询复杂度是O(n),n为list中的元素个数.所以list非常适合应用与数据插入删除频繁的场景. 一. list节点 list节点定义如下, struct _List_node_base { _List_node_base* _M_next; _List_node_base* _M_pre

stl源码分析之priority queue

前面两篇介绍了gcc4.8的vector和list的源码实现,这是stl最常用了两种序列式容器.除了容器之外,stl还提供了一种借助容器实现特殊操作的组件,谓之适配器,比如stack,queue,priority queue等,本文就介绍gcc4.8的priority queue的源码实现. 顾名思义,priority queue是带有优先级的队列,所以元素必须提供<操作符,与vector和list不同,priority queue允许加入元素,但是取出时只能取出优先级最高的元素. 一. pri

STL源码分析--仿函数 &amp; 模板的模板参数 &amp; 临时对象

STL源码分析-使用的一些特殊语法 关于泛型编程中用到的一些特殊语法,这些语法也适用于平常的模板编程 1.  类模板中使用静态成员变量 Static成员变量在类模板中并不是很特殊,同时这个变量不属于对象,属于实例化以后的这个类类型.每一个实例化对应一个static变量 2.  类模板中可以再有模板成员 3.  模板参数可以根据前一个模板参数而设定默认值 4.  类模板可以拥有非类型的模板参数 所谓非类型的模板参数就是内建型的模板参数 Template <class T,class Alloc =

STL源码分析--仿函数 &amp; 配接器

STL源码分析-仿函数 & 配接器 仿函数就是函数对象.就实现观点而言,仿函数其实就是一个"行为类似函数"的对象.为了能够"行为类似函数",其类别定义中必须自定义(或说改写.重载)function call 运算子(operator()),拥有这样的运算子后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator().仿函数作为可配接的关键因素. 配接器在STL组件的灵活组合运用功能上,扮演着轴承.转换器的角色,adapter的定

STL源码分析--空间配置器的底层实现 (二)

STL源码分析-空间配置器 空间配置器中门道 在STL中的容器里都是使用统一的空间配置器,空间配置器就是管理分配内存和销毁内存的.在STL将在heap空间创建一个对象分为两个步骤,第一是申请一块内存,第二是在这块内存中初始化一个对象.首先申请空间是由malloc提供,初始化一个对象时由constructor管理.销毁一个对象也是由两步骤完成,第一是销毁空间上的对象,第二是释放这块内存. 同时,STL的空间配置器分为两级内存,如果申请的内存空间大于128KB,那么就使用第一级空间配置,如果小于,那

STL 源码分析 # stl_iterator &amp; stl_iterator_base #

STL 源码分析 # stl_iterator_base && stl_iterator # 这里能很清楚的看到各个基础类型的继承关系 template <class _Tp, class _Distance> struct input_iterator { typedef input_iterator_tag iterator_category; typedef _Tp value_type; typedef _Distance difference_type; typede

STL源码分析--萃取编程(traits)技术的实现

1.为什么要出现? 按照默认认定,一个模板给出了一个单一的定义,可以用于用户可以想到的任何模板参数!但是对于写模板的人而言,这种方式并不灵活,特别是遇到模板参数为指针时,若想实现与类型的参量不一样的实例化,就变得不太可能了!也有时,想禁止此种相同的实例化变得不太可能!故而出现了,Partial Specialization! 同时,在使用void*指针时,可以最大限度的共享代码,减少代码的膨胀! 2.它是什么?其实,就是用户定义的偏特化.用template<>来说明这是一个偏特化,针对任何模板