allocator封裝了stl標准程序庫的內存管理系統,標准庫的string,容器,算法和部分iostream都是通過allocator分配和釋放內存的。標准庫的組件有一個參數指定使用的allocator類,比如vector的原型是:
template<typename _Tp, typename _Alloc = std::allocator<_Tp> > class vector : protected _Vector_base<_Tp, _Alloc>
第二個參數_Alloc指定使用的allocator,默認是std::allocator。我們也可以自己指定allocator
vector<int, __gnu_cxx::malloc_allocator<int> > malloc_vector;
將使用malloc_allocator分配釋放內存。
GNU gcc實現了多種allocator,下面簡單介紹幾種allocator的作用,我的g++版本是4.8。
1. new_allocator
這是g++4.8默認使用的allocator,即 std::allocator使用的allocator,在頭文件
/usr/include/c++/4.8/bits/allocator.h中定義了 std::allocator:
template<typename _Tp> class allocator: public __allocator_base<_Tp>
std::allocator使用的接口是由__allocator_base定義的,而後者在/usr/include/i386-linux-gnu/c++/4.8/bits/c++allocator.h定義為new_allocator:
# define __allocator_base __gnu_cxx::new_allocator
new_allocator只是簡單地包裝::operator new和operator delete,實現在/usr/include/c++/4.8/ext/new_allocator.h
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))); } void deallocate(pointer __p, size_type) { ::operator delete(__p); }
並沒有memory pool,所以現在如果還有程序因為使用了stl而出現內存沒有回收的問題,那麼一定是libc的cache沒有釋放,並不是stl的原因。
2. malloc_allocator
malloc_allocator直接包裝malloc和free,定義在/usr/include/c++/4.8/ext/malloc_allocator.h頭文件中,
pointer allocate(size_type __n, const void* = 0) { if (__n > this->max_size()) std::__throw_bad_alloc(); pointer __ret = static_cast<_Tp*>(std::malloc(__n * sizeof(_Tp))); if (!__ret) std::__throw_bad_alloc(); return __ret; } void deallocate(pointer __p, size_type) { std::free(static_cast<void*>(__p)); }
3. array_allocator
array_allocator並不調用new或者malloc從操作系統申請分配內存,而是直接使用已分配的內存。通過使用該allocator可以重用內存,效率很高。
array_allocator(array_type* __array = 0) _GLIBCXX_USE_NOEXCEPT : _M_array(__array), _M_used(size_type()) { } Pointer allocate(size_type __n, const void* = 0) { if (_M_array == 0 || _M_used + __n > _M_array->size()) std::__throw_bad_alloc(); pointer __ret = _M_array->begin() + _M_used; _M_used += __n; return __ret,_M_used }
_M_array指向已分配的內存塊地址,_M_used指向空閒的地址位移,初始值為0,每分配一段內存後就將_M_used後移.當需要的內存量超出空閒內存大小時會拋出bad_alloc異常。
4. debug_allocator
可以包裝任意其它的allocator,包括G++自帶的或者用戶自定義的,分配內存時多分配了一塊內存保存申請的內存大小,釋放時檢查釋放的內存大小是否和保存的值一樣,不一樣則拋出異常。具體的申請釋放動作由被包裝的allocator執行。
pointer allocate(size_type __n) { pointer __res = _M_allocator.allocate(__n + _M_extra); size_type* __ps = reinterpret_cast<size_type*>(__res); *__ps = __n; return __res + _M_extra; } void deallocate(pointer __p, size_type __n) { if (__p) { pointer __real_p = __p - _M_extra; if (*reinterpret_cast<size_type*>(__real_p) != __n) { throw std::runtime_error("debug_allocator::deallocate wrong size"); } _M_allocator.deallocate(__real_p, __n + _M_extra); } else throw std::runtime_error("debug_allocator::deallocate null pointer"); }
__ps中存儲了當前申請分配的內存長度, deallocate時會檢測釋放的內存大小是否等於該值。debug_allocator可以檢測內存是否存在洩露,代價是需要多分配用於debug的空間。
5. __pool_alloc
唯一一個帶內存池的allocator,也是G++早期默認使用的allocator,侯捷的《stl源碼剖析》第二章詳細分析了其代碼實現.原理就是為了減少內存碎片,當分配大於128byte的內存時直接調用operator new,而小於128byte的內存則從一個free list裡面取,free list中的內存是可以重用的,程序運行期間不會歸還給操作系統。過程比較復雜,有興趣的同學可以參考《stl源碼剖析》。
reference:
https://gcc.gnu.org/onlinedocs/gcc-4.9.1/libstdc++/manual/manual/memory.html#std.util.memory.allocato
實例化的時候不需要自己定義allocator。如果你要自己編寫allocator的話
1:沒有必要 2.對於新手來說很復雜。
所以你直接用默認的就可以了。默認的allocator是一個模板,會自動的幫你替換成allocator<string>的,所以你不需要管。
想用字符串的向量直接:std::vector<std::string>就可以了。
條款10:注意分配器的協定和約束
分配器是怪異的。它們最初是為抽象內存模型而開發的,允許庫開發者忽略在某些16位操作系統上near和far指針的區別(即,DOS和它的有害產物),但努力失敗了。分配器也被設計成促進全功能內存管理器的發展,但事實表明那種方法在STL的一些部分會導致效率損失。為了避免效率沖擊,C++標准委員會向標准中添加了詞語,把分配器弱化為對象,同時也表達了他們不會讓操作損失能力的希望。
還有更多。正如operator new和operator new[],STL分配器負責分配(和回收)原始內存,但分配器的客戶接口與operator new、operator new[]甚至malloc幾乎沒有相似之處。最後(而且可能非常驚人),大多數標准容器從未向它們相關的分配器索要內存。從沒有。結果分配器是,嗯,分配器是怪異的。
當然,那不是它們的錯,而且無論如何,這不意味著它們沒用。但是,在我解釋分配器好在哪裡之前(那是條款11的主題),我需要解釋它們哪裡不好。有許多事情分配器好像能做,但不能,而且在你試圖開始使用之前,知道領域的邊界很重要。如果不,你將肯定會受傷。此外,關於分配器的事實如此獨特,總結它的行為既有啟發性又有趣。至少我希望是。
分配器的約束的列表從用於指針和引用的殘留typedef開始。正如我提到的,分配器最初被設想為抽象內存模型,在那種情況下,分配器在它們定義的內存模型中提供指針和引用的typedef才有意義。在C++標准裡,類型T的對象的默認分配器(巧妙地稱為allocator<T>)提供typedef allocator<T>::pointer和allocator<T>::reference,而且也希望用戶定義的分配器也提供這些typedef。
C++老手立即發現這有問題,因為在C++裡沒有辦法捏造引用。這樣做要求有能力重載operator.(“點操作符”),而那是不允許的。另外,建立行為像引用的對象是使用代理對象的例子,而代理對象會導致很多問題。(一個這樣的問題產生了條款18。對代理對象的綜合討論,轉向《More Effective C++》的條款30,你能知道什麼時候它們工作什麼時候不。)
就STL裡的分配器而言,沒有任何代理對象的技術缺點會導致指針和引用typedef失效,實際上標准明確地允許庫實現假設每個分配器的pointer typedef是T*的同義詞,每個分配器的reference typedef與T&相同。對,庫實現可以忽視typedef並直接使用原始指針和引用!所以即使你可以設法寫出成功地提供新指針和引用類型的分配器的方法,也好不到哪裡去,因為你使用的STL實現將自由地忽視你的typedef。很優雅,不是嗎?
當你欽佩標准化的怪癖時,我將再介紹一個。分配器是對象,那表明它們可能有成員功能,內嵌的類型和typedef(例如pointer和reference)等等,但標准允許STL實現認為所有相同類型的分配器對象都是等價的而且比較起來總是相等。很唐突,聽起來並不可怕,而且對它當然有好的動機......余下全文>>
C++模板的聲明
你看到這個應該是STL中容器(vector, list)的代碼
這個模板包含兩個模板參數,T 和 Allocator。其中 Allocator 有 缺省值,缺省為 allocator<T>
allocator 也是一個模板,需要一個參數,你可以具體看它的定義,allocator<T> 為 模板allocator的實例。
要想看懂STL,需要一定的模板功底,推薦閱讀 《C++ Template》。