C++ allocator


說一說C++里的allocator。我們知道,C++ STL里定義了很多的容器(containers),每一個容器的第二個模板參數都是allocator類型。比方說在VC10里,vector類的模板聲明為:

  template<class _Ty, class _Ax = allocator<_Ty> >
  class vector

但是,基本上很少有人會自定義一個allocator。一來,默認的allocator已經夠用了;二來,確實不知道該怎么用。一般來說,我們沒有必要重新定義一個allocator。自定義的方式主要是為了提高內存分配相關操作的性能。而STL提供的方式性能已經足夠好了。事實上,在windows平台上,new的底層實現是基於C語言的malloc函數;malloc函數家族又是基於Windows HeapCreate、HeapAlloc、HeapFree等相關API來實現的(具體可以參考%VSInstallFolder%\VC\crt\src目錄中的heapinit.c、malloc.c和new.cpp等相關函數)。

先撇開性能的問題不說,我們看一看如何實現一個自己的allocator。

在C++ 2003標准文檔里,關於allocator的說明其實並不多,大概就20.1.5 Allocator requirements20.4.1 The default allocator兩處主要位置。雖然內容不多,但是足夠我們寫出一個自己的allocator。

根據Allocator requirements我們需要提供一些typedefs:

   1: template <typename T>
   2: class CHxAllocator
   3: {
   4: public:
   5:     // typedefs...
   6:     typedef T                   value_type;
   7:     typedef value_type*         pointer;
   8:     typedef value_type&         reference;
   9:     typedef value_type const*   const_pointer;
  10:     typedef value_type const&   const_reference;
  11:     typedef size_t              size_type;
  12:     typedef ptrdiff_t           difference_type;
  13:  
  14:     // rebind...
  15:     template <typename _other> struct rebind { typedef CHxAllocator<_other> other; };
  16: };

在這里有一個比較不太容易理解的東西:rebind。C++標准里這么描述rebind的:

The member class template rebind in the table above is effectively a typedef template: if the name Allocator is bound to SomeAllocator<T>, then

Allocator::rebind<U>::other is the same type as SomeAllocator<U>.

啥意思?可以用一個簡單的例子來說明下:

學校都學過數據結構,比方說棧、單向列表、樹。我們就拿棧和列表來對比,看看有什么大不一樣的地方。撇開數據結構上的差異,從allocator的角度來看,我們可以發現:堆棧是存貯元素本身的,但是列表實際上不是直接存儲元素本身的。要維護一個列表,我們至少還需要一個所謂的next的指針。因此,雖然是一個保存int的列表list<int>,但是列表存儲的對象並不是int本身,而是一個數據結構,它保存了int並且還包含指向前后元素的指針。那么,list<int, allocator<int>>如何知道分配這個內部數據結構呢?畢竟allocator<int>只知道分配int類型的空間。這就是rebind要解決的問題。通過allocator<int>::rebind<_Node>()你就可以創建出用於分配_Node類型空間的分配器了。

接下來要提供其他的接口。根據The default allocator的描述,我們要提供如下一些接口:

pointer address(reference val) const
const_pointer address(const_reference val) const
返回val的地址
pointer allocate(size_type cnt, CHxAllocator<void>::const_pointer pHint = 0) 分配空間。類似malloc。pHint可以無視,主要是給類庫使用,用於提高性能。
void deallocate(pointer p, size_type n) 釋放空間,類似free。
size_type max_size() const throw() 可分配的最大數量。
void construct(pointer p, const_reference val) 在地址p所指向的空間,使用val進行填充。需要使用到palcement new,以便保證調用到構造函數。
void destroy(pointer p) 析構p指向的內存塊中內容。一般通過顯示調研析構函數來執行。

allocator() throw ()
allocator(const_reference) throw ()
template <typename _other> allocator(CHxAllocator <_other> const&) throw()
~CHxAllocator() throw()

各種構造函數和析構函數

如何實現上面這些函數,你只要照抄標准庫中的實現就可以了。如果你想要用c的malloc和free來實現,也可以這么寫:

   1: pointer allocate(size_type cnt, CHxAllocator<void>::const_pointer pHint = 0)
   2: {
   3:     UNREFERENCED_PARAMETER(pHint);
   4:  
   5:     if (cnt <= 0)
   6:     {
   7:         return 0 ;
   8:     }
   9:  
  10:     void* pMem = nullptr ;
  11:     if (max_size() < cnt || (pMem = malloc(cnt * sizeof(value_type))) == NULL)
  12:     {
  13:         throw std::bad_alloc(0);
  14:     }
  15:  
  16:     return static_cast <pointer>(pMem);
  17: }
  18:  
  19: void deallocate(pointer p, size_type)
  20: {
  21:     free(p);
  22: }
  23:  
  24: void construct(pointer p, const_reference val)
  25: {
  26:     :: new ((void *)p) T(val);
  27: }
  28:  
  29: void destroy(pointer p)
  30: {
  31:     p->~T();
  32: }

基本上,我們就簡單實現了一個自己的allocator。另外,除了這些最主要的接口函數,你還需要實現比較操作符==和!=,但是這些函根據標准文檔,都直接返回true和false。

開頭已經說了,重寫allocator的主要目的是為了提高性。那怎樣才能提高性能呢?直接使用Windows的HeapXXXX堆內存API?其實,你自己用一下就會發現,性能提升並不明顯。因為通過new,再通過malloc,最后通過HeapAlloc不比直接調用HeapAlloc多幾句話。如何實現一個高性能的allocator,需要借助memory pool的想法。另外,侯捷的stl源碼剖析里分析了SGI STL利用類似想法實現的一個alloc。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM