說一說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 requirements和20.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 () |
各種構造函數和析構函數 |
如何實現上面這些函數,你只要照抄標准庫中的實現就可以了。如果你想要用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。