STL 源碼分析六大組件-allocator

1. allocator 基本介紹html

分配器(allocator))是C ++標準庫的一個組件, 主要用來處理全部給定容器(vector,list,map等)內存的分配和釋放。C ++標準庫提供了默認使用的通用分配器std::allocator,可是,也能夠由程序員本身提供自定義分配器。 c++

2. allocator 標準庫規範程序員

咱們去看std中的stl分配器實現,會發現不管你的實現思路怎麼變,全部模板類中的接口和成員變量都是同樣的,那麼這是由於C++標準庫在制定分配器時,是有提出硬性標準的,具體接口和成員變量以下: 緩存

STD 標準規範(GNU ISO C++ Library 5.2.1):多線程

typedef size_t     size_type; 
typedef ptrdiff_t  difference_type;
typedef _Tp*       pointer; 
typedef const _Tp* const_pointer;
typedef _Tp&       reference;
typedef const _Tp& const_reference;
typedef _Tp        value_type;

void construct(pointer __p, const _Tp& __val) { ::new((void *)__p) value_type(__val); }

void destroy(pointer __p) { __p->~_Tp(); }

size_type max_size() const _GLIBCXX_USE_NOEXCEPT { return size_t(-1) / sizeof(_Tp); }

address(const_reference __x) const _GLIBCXX_NOEXCEPT

deallocate(pointer, size_type);

allocate(size_type __n, const void* = 0);

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

對於上面這一段接口和變量,其中一些在其餘容器的分析中,咱們會常常看到,下面對於其中有疑問的點咱們來解釋一下:性能

size_type:this

爲 unsigned 類型 , 表示容器中元素長度或者下標,例:vector ::size_type i = 0;咱們都知道 size_t 在32位和64位系統上是不同的,size_t已經能夠解決平臺差別了,那爲何還要引入size_type,這裏咱們能夠理解爲size_t是屬於全局的,而size_type是跟容器相關的,是屬於STL的一套,在其餘容器中也是同樣的。 spa

difference_type:.net

爲 signed 類型 , 表示迭代器差距, vector :: difference_type = iter1-iter2,ptrdiff_t一般用來保存兩個指針減法操做的結果。 線程

size_t(-1):

size_t 爲 unsigned 類型,傳入 -1 得到size_t該系統的最大值。

3. GNU ISO C++ Library 中STL分配器實現

1. 默認分配器:

按照標準規範,標準庫中stl實現的分配器,對外接口,成員變量幾乎都同樣,只是接口內部實現有區別,那麼咱們看一下具體實現流程, 首先看一下容器默認的分配器std::allocator<_Tp>。 看代碼咱們會發現,默認分配器真正的實現是在new_allocator.h頭文件中作的,頭文件具體調用順序爲:allocator.h -> c++allocator.h -> new_allocator.h, 看代碼咱們知道,在前面兩個頭文件中基本沒作什麼,真正的實現爲模板類new_allocator。 關鍵代碼以下所示:

namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
  template<typename _Tp>
    class new_allocator
    {
    public:
      typedef _Tp*       pointer;
      typedef const _Tp* const_pointer;

      // NB: __n is permitted to be 0.  The C++ standard says nothing
      // about what the return value is when __n == 0.
      pointer
      allocate(size_type __n, const void* = 0)
      { 
	if (__n > this->max_size())
	  std::__throw_bad_alloc();

	return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
      }

      // __p is not permitted to be a null pointer.
      void
      deallocate(pointer __p, size_type)
      { ::operator delete(__p); }
    }
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

咱們看一下分配和釋放的接口allocate和deallocate,實現上只是單純的將::operator new和::operator delete進行了一下封裝,沒用作特殊處理。

固然,若是咱們不特別說明,直接在Linux系統下使用標準C++的stl容器,在爲容器元素分配內存時,使用的方式就是相似於正常的new和delete,這種方式對於不頻繁分配,而且一次分配大塊內存的使用狀況是適用的,典型的容器像vectordeque

2. 拓展分配器:

咱們繼續查看STL源碼會發現,除了默認分配器,gnu下的STL還實現了一些特殊的分配器,像__pool_alloc__mt_allocarray_allocatormalloc_allocator (在源碼的/c++/ext/ 目錄下)

__pool_alloc :比較出名的SGI內存池分配器,侯捷老師在STL源碼剖析中着重分析的,後面會專門加一章來進行講解

__mt_alloc : 多線程內存池分配器,具體能夠看一下該博主的連接講解,仍是比較詳細的

array_allocator : 全局內存分配,只分配不釋放,交給系統來釋放

malloc_allocator : 封裝了一下std::malloc和std::free

4. 自定義分配器 (allocator)

咱們爲何要自定義分配器,廢話,主要緣由固然是由於性能了。利用自定義分配器能夠顯著改善程序性能及分配器使用便利性。

對於默認分配器,也就是咱們常使用的new和malloc,若是遇到須要頻繁分配小塊內存對象的狀況,由於須要不停的向系統鎖要內存,那麼將使得分配過程變得很慢;還有就是致使出現過多的內存碎片;以及一些極端狀況,分配的內存過於小且數量龐大,致使分配一次內存,附帶的內存消耗反而更大,形成內存的浪費

主流提升性能的方式是建立基於內存池的分配器,每次在容器中插入刪除元素,不是分配內存,而是分配程序啓動時預先分配的大塊內存(內存池)。這種自定義分配器,經過簡單的從池中返回指向內存的指針來提供單獨的分配請求。還有就是能夠推遲實踐的內存釋放,直到內存池生命週期結束

C++之父 Bjarne stroustrup 提到,自定義分配器的三個主要應用,即內存池分配器,共享內存分配器和垃圾收集(gc)分配器

垃圾回收應該就是智能指針,本身控制資源的回收;內存池則主要負責對性能的提高;共享內存分配器則是對特殊緩存區的處理。 後續會一一分析介紹!

具體連接:

SGI 內存池分配器
智能指針
共享內存分配-hashmap
多線程分配器

2018年9月8日00:18:28

相關文章
相關標籤/搜索