GUN C++ STL中的vector的記憶體分配器

王加明發表於2017-10-17

GUN C++ STL中的vector的記憶體分配器

//vector 原型  預設情況下其記憶體配置器為std::allocator<_Tp>
template <typename _Tp, typename _Alloc = std::allocator<_Tp>>
class vector : protected _Vector_base<_Tp, _Alloc>;
//_Vector_base原型
template <typename _Tp, typename _Alloc>
struct _Vector_base{
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;//stl原始碼剖析上說, 這個其實就是一個allocator<U>, 見alloc_traits
//_Vector_base的一個內部類, 繼承了這個allocator
struct _Vector_impl : _Tp_alloc_type{
    pointer _M_start;//目前使用的空間的開始指標
    pointer _M_finish;//目前使用的空間的結尾指標
    pointer _M_end_of_storage;//目前可用的空間的尾指標
};
};

看一個vector的建構函式

//呼叫形式:vector<string> v(10, "asd");
vector( size_type __n, //建構函式結束時,會析構這個臨時物件allocator
        const value_type &__value, 
        const allocator_type &__a = allocator_type())//此處構造一個臨時的allocator物件
    : _Base(__n, __a){//基類複製了這個臨時物件, 生成了自己的內部類
    _M_fill_initialize(__n, __value);//這個是負責構造物件的
}
//呼叫基類的這個建構函式
//發現父類中並沒有負責物件的構造,只是負責記憶體的分配
_Vector_base(size_t __n, const allocator_type &__a) : _M_impl(__a){//呼叫_M_impl複製構造
    _M_create_storage(__n);//記憶體貌似在這兒分配的
}
//呼叫這個內部類的建構函式
_Vector_impl(_Tp_alloc_type const& __a) _GLIBCXX_NOEXCEPT
    : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0){}
//又呼叫其父類的_Tp_alloc_type複製建構函式,就是std::allocator<_Tp>
//這是一個基類的protected函式
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;
}
//這是一個基類的public函式
pointer _M_allocate(size_t __n){//分配的記憶體大小與呼叫vector時設定的大小一致
  //預設情況下_Tp_alloc_type是std::allocator<_Tp>, 見alloc_traits
  typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tr;
  return __n != 0 ? _Tr::allocate(_M_impl, __n) : 0;//呼叫該構造器的allocate函式分配記憶體
}

再看_M_fill_initialize

void _M_fill_initialize(  size_type __n, const value_type &__value){
    std::__uninitialized_fill_n_a(this->_M_impl._M_start, 
                                  __n, 
                                  __value, 
                                  _M_get_Tp_allocator());
    this->_M_impl._M_finish = this->_M_impl._M_end_of_storage;
}
//標頭檔案 stl_uninitialized.h中
//此函式還有一個標準模版的版本, 最後一個引數是為了使用預設記憶體分配器std::allocator時使用這個版本
template<typename _ForwardIterator, typename _Size, typename _Tp, typename _Allocator>
void __uninitialized_fill_n_a(_ForwardIterator __first, 
                              _Size __n, 
                              const _Tp& __x, 
                              _Allocator& __alloc);//使用自己的分配器時呼叫此函式
template <typename _ForwardIterator, typename _Size, typename _Tp, typename _Tp2>
inline void __uninitialized_fill_n_a(_ForwardIterator __first, 
                                     _Size __n, 
                                     const _Tp& __x, 
                                     allocator<_Tp2>&){//最後一個引數只是為了函式匹配
  std::unitialialized_fill_n(__first, __n, __x);
}
//unitialialized_fill_n
 template<typename _ForwardIterator, typename _Size, typename _Tp>
 inline void uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x){
    //value_type 是迭代器所指向的型別, vector的迭代器其實是一個Random Access Iterator
    typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
#if __cplusplus < 201103L
      const bool __assignable = true;
#else
      // trivial types can have deleted assignment
      //利用traits技法"萃取"迭代器所指物件的型別是否支援'複製構造'和'operator='操作
      //不過貌似這個一直都是true,即使這兩個函式被宣告為delete
      const bool __assignable = is_copy_assignable<_ValueType>::value;
#endif
    //__is_trivial 提取迭代器所指物件是否有預設的建構函式
      std::__uninitialized_fill_n<__is_trivial(_ValueType) && __assignable>::
    __uninit_fill_n(__first, __n, __x);
}
//如果有預設構造
//其中呼叫全域性construct函式, 構造物件
//如果沒有預設夠造函式
//std::__uninitialized_fill_n<true>::__uninit_fill_n函式, 最終呼叫fill_n

vector是如何管理分配的記憶體大小的?

//左值引用版本
//呼叫push_back(_Tp &)時, 會檢查記憶體分配情況, 夠不夠新插入的元素
//_Alloc_traits  typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Alloc_traits;
void push_back(const value_type &__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, __x);
        ++this->_M_impl._M_finish;//有就直接構造物件了
    }else//沒有就分配空間再構造
#if __cplusplus >= 201103L
        _M_emplace_back_aux(__x);
#else
        _M_insert_aux(end(), __x);
#endif
}
//右值引用版本
//_M_emplace_back_aux  
//宣告在stl_vector.h中, 定義在vector.tcc檔案中
#if __cplusplus >= 201103L
template<typename _Tp, typename _Alloc>
template<typename... _Args>
void vector<_Tp, _Alloc>::_M_emplace_back_aux(_Args&&... __args){
    //從_M_check_len函式可以看出vector的記憶體分配策略
    //原大小加上max(size(), 1)        __n就是size_type(1)
    //const size_type __len = size() + std::max(size(), __n);
    const size_type __len = _M_check_len(size_type(1), "vector::_M_emplace_back_aux");
    pointer __new_start(this->_M_allocate(__len));//開始分配記憶體
    pointer __new_finish(__new_start);//調節尾指標
/*---------------------------------------------------------------------------------*/
    __try{//為了在構造了部分物件時,發生了異常,可以析構掉之前已經構造的物件    
        _Alloc_traits::construct(this->_M_impl, //先構造這個push_back的元素
                                __new_start + size(), //在start+size()位置構造
                                std::forward<_Args>(__args)...);//forward保持其左/右值引用屬性
        __new_finish = 0;
        __new_finish//移動元素,如果可以移動的話   C++primer 第五版 p474
          = std::__uninitialized_move_if_noexcept_a
          (this->_M_impl._M_start, this->_M_impl._M_finish,
           __new_start, _M_get_Tp_allocator());
        ++__new_finish;
      }__catch(...){//如果出現異常
        if (!__new_finish)//如果在設定__new_finish = 0之前構造物件就出現異常,析構這一個物件
          _Alloc_traits::destroy(this->_M_impl, __new_start + size());
        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());//析構物件
    _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;//調節指標
}
#endif

allocator_traits

這個類中有記憶體配置器的大部分介面函式, 只是引數加一個allocator_type, 然後呼叫

//模版定義
template <typename _Alloc>
struct allocator_traits : __allocator_traits_base;
//std::allocator即stl預設的記憶體配置器的特例化版本
template <typename _Tp>
struct allocator_traits<allocator<_Tp>>;

相關文章