一个return引发的血案 - 自己动手实现allocator
? ? 最近在追舊番《STL代碼剖析》。真的是很舊很舊的番了,STL在94年開始走入STL,這本書則是2002年出版的,C++03和C++11還不知何在的年代??赐甑诙轮蠛仙蠒?#xff0c;想自己寫一個allocator。發現看書過程中自認為“所言極是”的地方,居然根本寫不出來。雖然從前也寫過內存池 (mempool),但是STL的手法和我等閑輩果然不是一個層次。于是只好邊寫邊翻書,也算順利得寫出來了一個不支持fill和copy的版本。畢竟,stl_uninitialized相對于stl_construct和stl_alloc簡單多了,我個人也不常用它來初始化容器。
?
? ? 本來一個簡單的事情,寫完后發現malloc_alloc好用,default_alloc卻不好用,也是,memory pool自然比最naive的placement new 和 free要簡單了。檢查到最后,發現似乎alloc忘記寫return res了!加上return運行,走起!發現自己寫程序,總是寫到最后就忘記return了,果然愛旅行的人還是太年輕,總是忘記回家的路呢。
? ? (PS:需要-std_c++11哦,因為我是在受不了C98里面的尖括號問題!而且C11里的unordered_map等類的性能也比C98里的hashtable好,auto關鍵字也非常好用。)
? ? (順便回顧回顧auto_ptr、placement new、#pragma pack(n)這些最近才長的知識。)
內存分配:
1,大塊直接malloc和free,內存不夠時使用handler試圖產生更多內存;
2,小塊內存使用內存池,避免因為小塊內存散落在各處,造成大量外部碎片,同時提高內存分配效率。
/*my_alloc.h */ #include <cstdlib> //for malloc and free #include "my_traits.h"#if 0 # include <new> # define __THROW_BAD_ALLOC throw bad_alloc; #else # include <iostream>using namespace std; # define __THROW_BAD_ALLOC {cerr<<"out of memory"<<endl; exit(1);}; #endif//#define __USE_MALLOC_ALLOC 0/* first level allocator */ /* __malloc_alloc_template */ template<int inst> class __malloc_alloc_template { private://out of memory//keep trying alloc until succeedstatic void* oom_alloc(size_t bytes){set_malloc_handler(0);void * res = 0;while(1){if(!__malloc_alloc_oom_handler) {__THROW_BAD_ALLOC;}(*__malloc_alloc_oom_handler)();if(res = malloc(bytes)) return res;}}static void* oom_realloc(void* p, size_t bytes){set_malloc_handler(0);void * res = 0;while(1){if(!__malloc_alloc_oom_handler) __THROW_BAD_ALLOC;(*__malloc_alloc_oom_handler)();if(res = realloc(p, bytes)) return res;}}static void (* __malloc_alloc_oom_handler)();//set the malloc handler function as fstatic void (* set_malloc_handler( void (*f)() )) (){void (*old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return old;} public:static void* allocate(size_t bytes){void * res = malloc(bytes);if(!res) res = oom_alloc(bytes);return res;}static void deallocate(void* p, size_t bytes){/*char * q = p;while(bytes--){free(q);q++;}*/free(p);}static void* reallocate(void* p, size_t bytes){void * res = realloc(p, bytes);if(!res) res = oom_realloc(p, bytes);return res;} }; template<int inst> void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;typedef __malloc_alloc_template<0> malloc_alloc;/* end of __malloc_alloc_template *///memory pool /* __default_alloc_template */enum {MIN_BLOCK = 8, MAX_BLOCK = 128, NFREELISTS = MAX_BLOCK/MIN_BLOCK};union obj {obj* free_list_link;char client_data[1]; };template <bool threads, int inst> class __default_alloc_template {static obj* freelist[NFREELISTS];//start of memory poolstatic char* start_free;//end of memory poolstatic char* end_free;//size of the whole memory poolstatic size_t heap_size;private://n -> 8*m//eg. 7->8, 8->8, 9->16static size_t ROUND_UP(size_t n){//n -> freelist_index//eg. 7->0, 8->0, 9->1, 16->1return (n + MIN_BLOCK - 1) & ~(MIN_BLOCK-1);}static size_t FREELIST_INDEX(size_t n){return (n/MIN_BLOCK - 1);}static char* chunk_alloc(size_t size, int & nobjs){char* res = 0;size_t bytes_needed = nobjs * size;size_t bytes_left = end_free - start_free;//if the left space meets the demand, allocate and returnif(bytes_needed <= bytes_left){res = start_free;start_free += bytes_needed;return res;}//if the left space partially meets the demand, allocateif(size <= bytes_left){res = start_free;nobjs = bytes_left/size;start_free += nobjs * size; //note that nobjs*size != bytes_leftreturn res;}//if the left space cannot meets even one block demand,size_t bytes_to_get = 2 * bytes_needed + ROUND_UP(heap_size >> 4); //bytes which are to be gained.//put the left part into the correct freelistif(bytes_left > 0){obj** my_freelist = freelist + FREELIST_INDEX(bytes_left);((obj*)(start_free))->free_list_link = *my_freelist;*my_freelist = (obj*)(start_free);}//and try to gain more space from the heap.start_free = static_cast<char*>(malloc(bytes_to_get));if(0 == start_free){int i;obj ** my_freelist;//search unused blocks in the freelist whose size le "size", and reuse it//otherwise the space in the freelist might grow huge.for(i=size; i<MAX_BLOCK; i+=MIN_BLOCK){my_freelist = freelist + FREELIST_INDEX(i);obj* p = *my_freelist;if(p){*my_freelist = p->free_list_link;start_free = (char*)(p);end_free = start_free + i;//now end_free - start_free == i >= sizereturn (chunk_alloc(size, nobjs));}}//end_free = 0; /?//turn to malloc_alloc::oom_alloc if failed.start_free = static_cast<char*>(malloc_alloc::allocate(bytes_to_get));}end_free = start_free + bytes_to_get;heap_size += bytes_to_get;return chunk_alloc(size, nobjs);}//n should be 8*mstatic void* refill(size_t n){//the default refill number of the block of size nint nobjs = 20;char* chunk = chunk_alloc(n, nobjs);obj** my_freelist = freelist + FREELIST_INDEX(n);obj* res;obj* cur;int i;//if chunk_alloc only return 1 block;if(1 == nobjs) return (chunk);//else if many blocks are chunkedres = (obj*)(chunk);*my_freelist = cur = (obj*)((char*)(res)+n);for(i=2; i<nobjs; i++){cur->free_list_link = (obj*)((char*)(cur)+n);cur = cur->free_list_link;}cur->free_list_link = 0;return res;}public:static void * allocate(size_t bytes){if(MAX_BLOCK < bytes) return malloc_alloc::allocate(bytes);//gain a space from freelistobj** my_freelist = freelist + FREELIST_INDEX(bytes);obj* res = *my_freelist;if(0 == res){return refill(ROUND_UP(bytes));}*my_freelist = res->free_list_link;return res;}static void deallocate(void* p, size_t bytes){if(MAX_BLOCK < bytes){ malloc_alloc::deallocate(p, bytes);return; }//return the deallocated space to freelist;obj** my_freelist = freelist + FREELIST_INDEX(bytes);static_cast<obj*>(p)->free_list_link = *my_freelist;*my_freelist = static_cast<obj*>(p);} };template <bool threads, int inst> char* __default_alloc_template<threads, inst>::start_free = 0; template <bool threads, int inst> char* __default_alloc_template<threads, inst>::end_free = 0; template <bool threads, int inst> size_t __default_alloc_template<threads, inst>::heap_size = 0; template <bool threads, int inst> obj * __default_alloc_template<threads, inst>::freelist[NFREELISTS]={0};/* end of __default_alloc_template */#define __NODE_ALLOCATOR_THREADS 0#ifdef __USE_MALLOC_ALLOCtypedef malloc_alloc alloc; #elsetypedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc; #endif // __USE_MALLOC/* simple_alloc */ template<class T, class Alloc=alloc> class simple_alloc { public:typedef T value_type;typedef T* pointer;typedef const T* const_pointer;typedef T& reference;typedef const T& const_reference;typedef size_t size_type;typedef ptrdiff_t difference_type; public:static T* allocate(size_t n){if(n==0) return 0;return static_cast<T*>(Alloc::allocate(n*sizeof(T)));}static const T* allocate(void){return Alloc::allocate(sizeof(T));}//釋放多個static void deallocate(T* p, size_t n){if(0!=n) Alloc::deallocate(p, n*sizeof(T));}//釋放一個static void deallocate(T* p){Alloc::deallocate(p, sizeof(T));}}; /* end of simple_alloc *//* end of my_alloc.h */? ? ?對象構造與析構:根據type_traits進行析構(私以為構造也可如此,如int等基本類型則不必構造,直接malloc出來即可用)
? ??
/* my_constructor.h */#include <iostream> #include "my_traits.h" using namespace std;template<class T, class T1>inline void construct(T* p, const T1& value){new (p) T1(value);}template<class T>inline void destroy(T* p){typename __my_traits<T>::has_trivial_destructor a;return destroy(p, a);}template<class T>inline void destroy(T* p, __true_){cerr << "I am a trivial destructor"<<endl;}template<class T>inline void destroy(T* p, __false_){cerr << "I am not a trivial destructor."<<endl;p->~T();}template<class T>inline void destroy(T* p, T* t){typename __my_traits<T>::has_trivial_destructor a;return destroy(p, t, a);}template<class T>inline void destroy(T* f, T* t, __true_){}template<class T>inline void destroy(T* f, T* t, __false_){while(f<t){destroy(f);f++;}}inline void destroy(char *, char * p=0){}inline void destroy(wchar_t *, wchar_t * p=0){}inline void destroy(int *, int * p=0){}inline void destroy(size_t *, size_t * p=0){}inline void destroy(long long *, long long * p=0){}// .../* end of my_constructor.h*/? ? ?類型特征萃取(type_traits):通過模板傳給traits,再通過統一的類型名稱傳遞類型信息(要求用戶類中定義好那些統一的類型名稱,并用true或false指定實際的類型)
? ? 類型特征全部用不同的數據類型表示,而不用數值(宏、枚舉等)表示:
1,提高效率,數值需要在運行時加以判斷,而數據類型是編譯階段決定好的
? ? ?2,針對不同的類型特征,可定義重載函數而不是在同一個函數中加入if else分支,使得代碼更加清晰。
? ? type_traits最主要的作用,是使得不同類型的類得以被區別對待,避免為無作為的構造或析構付出不必要的函數調用代價。
/* my_traits.h */#ifndef __TRUE_ #define __TRUE_ struct __true_ {}; #endif // __TRUE_ #ifndef __FALSE_ #define __FALSE_ struct __false_ {}; #endif // __FALSE_#ifndef __MY_TRAITS #define __MY_TRAITS template <class _Tp> struct __my_traits {typedef __true_ this_dummy_member_must_be_first;// 不要移除這個成員// 它通知能自動特化__type_traits的編譯器, 現在這個__type_traits template是特化的// 這是為了確保萬一編譯器使用了__type_traits而與此處無任何關聯的模板時// 一切也能順利運作// 以下條款應當被遵守, 因為編譯器有可能自動生成類型的特化版本// - 你可以重新安排的成員次序// - 你可以移除你想移除的成員// - 一定不可以修改下列成員名稱, 卻沒有修改編譯器中的相應名稱// - 新加入的成員被當作一般成員, 除非編譯器提供特殊支持 typedef __false_ has_trivial_default_constructor;typedef __false_ has_trivial_copy_constructor;typedef __false_ has_trivial_assignment_operator;typedef __false_ has_trivial_destructor;typedef __false_ is_POD_type; };template <> struct __my_traits<int> {typedef __false_ has_trivial_default_constructor;typedef __false_ has_trivial_copy_constructor;typedef __false_ has_trivial_assignment_operator;typedef __false_ has_trivial_destructor;typedef __false_ is_POD_type; };#endif // __MY_TRAITS/* end of my_traits.h */? ? ?通常為某些可以批量初始化的容器而設計的fill和copy函數,通過type_traits提取出具體的類型信息,并區別對待:
? ? ?1,對于trivial的類,只需要調用memcopy等簡單地內存函數即可;
? ? ?2,對于復雜的類,則需要調用(復制)構造函數,以避免淺復制帶來的錯誤。
? ? (部分函數體未補充完整)
/* my_uninitialized.h */#include "my_traits.h" #include <cstring>template<class T> T* uninitialized_fill(T* f, T* t, const T& x) {typename __my_traits<T>::is_POD_type a;return uninitialized_fill(f, t, x); } template<class T> T* uninitialized_fill(T* f, T* t, const T& x, __true_) { } template<class T> T* uninitialized_fill(T* f, T* t, const T& x, __false_) {}template<class T> T* uninitialized_fill_n(T* f, size_t n, const T& x) {typename __my_traits<T>::is_POD_type a;return uninitialized_fill(f, t, x, a); } template<class T> T* uninitialized_fill_n(T* f, size_t n, const T& x, __true_) {return fill_n(f, n, x); } template<class T> T* uninitialized_fill_n(T* f, size_t n, const T& x, __false_) {while(f<t){construct(f, x);f++;} }template<class T> T* uninitialized_copy(const T* srcs, const T* srcf, T* dest) { } template<class T> T* uninitialized_copy(const T* srcs, const T* srcf, T* dest, __true_) {return copy(srcs, srcf, dest); } template<class T> T* uninitialized_copy(const T* srcs, const T* srcf, T* dest, __true_) { }/* end of my_uninitialized.h */? ? 最后試著用上面的配置器(allocator、constructor和uninitialized)寫一個簡單的vector容器:
/* vector.h */ #include <memory> #include "my_alloc.h" #include "my_construct.h"template <class T, class Alloc=alloc> class vector { public:typedef T value_type;typedef T* pointer;typedef T* iterator;typedef T& reference;typedef size_t size_type;typedef ptrdiff_t difference_type;typedef simple_alloc<T, Alloc> alloc;iterator start;iterator finish;iterator storige_end;public:vector():start(0), finish(0), storige_end(0) {}//加上explicite防止編譯器進行隱式轉換explicit vector(size_type n){start = Alloc::allocate(n);uninitialized_fill_n(start, n, T()); //此時T必須有空參數的構造函數,(否則會不會調用默認構造函數?)finish = start + n;}vector(size_type n, const T& x){start = Alloc::allocate(n);uninitialized_fill_n(start, n, T(x));finish = start + n;}vector(iterator from, iterator to){size_type len = static_cast<size_type>(to-from);start = Alloc::allocate(len);uninitialized_copy(from, to, start);finish = start + len;}~vector(){if(start){destroy(start, finish);Alloc::deallocate(start, capacity());}}const iterator begin() {return start;}const iterator end() {return finish;}size_type size() const{return static_cast<size_type>(finish - start);}size_type capacity() const{return static_cast<size_type>(storige_end - start);}bool empty() const{return start==finish;}//真的需要?void deallocate(){start && Alloc::deallocate(start, capacity());}void push_back(const T& x){insert(finish, x);}//注意,諸如fill和copy這種將數據源復制到多個變量的函數,//要考慮到所修改的內存空間是否會覆蓋到數據源所在的空間void insert(iterator pos, const T x){//空間足夠if(finish!=storige_end){construct(finish, *(finish-1));uninitialized_copy(pos, finish, pos+1);*pos = x;finish++;}else{size_type old_size = size();size_type new_size = 1;iterator new_start, new_finish;if(old_size) new_size = old_size << 1;new_start = new_finish = Alloc::allocate(new_size);//能否不用try catchtry{new_finish = uninitialized_copy(start, pos, new_start);construct(new_finish++, x);new_finish = uninitialized_copy(pos, finish, new_finish);}catch(...){if(new_start){//釋放之前真的需要析構?//需要。因為T變量中可能包含指向配置器以外的其他空間的指針。 destroy(new_start, new_finish);Alloc::deallocate(new_start, new_size);}throw;}//釋放之前真的需要析構?//需要!因為T變量中可能包含指向配置器外的其他空間的指針if(start){destroy(start, finish);//釋放 Alloc::deallocate(start, old_size);}//調整迭代器start = new_start;finish = new_finish;storige_end = new_start + new_size;}}void pop_back(){Alloc::destroy(--finish);} }; /* end of vector.h */? ? 最后測試,用簡單類型、string類型(測試深復制)和自定義的結構體類型:
/* main.cpp */#include <iostream> #include "vector.h" #include <auto_ptr.h>/* main-my-vector.cpp*/ #pragma pack(4) struct s {int i;int j;char c;explicit s(int _i, int _j=0):i(_i), j(_j){}~s(){} }; #pragma pack()ostream & operator << (ostream & o, const s& _s) {o << "(" <<_s.i<< ", " <<_s.j<<")";return o; }main() {typedef string T;cout <<"sizeof(T) "<<sizeof(T)<<endl;auto_ptr<vector<T, simple_alloc<T>>> pv(new vector<T, simple_alloc<T>> ());vector<T, simple_alloc<T>> &v = (*pv);//vector<T, simple_alloc<T>> v;for(int i=0; i<10; i++){v.push_back(T("xx"));cout <<v.capacity()<<endl;}vector<T, simple_alloc<T>>::iterator it;for(it=v.begin(); it!=v.end(); it++)cout <<(*it)<<endl;cout <<endl; } /* end of main.cpp */?
? ? ?看過jjhou翻譯的Effective C++系列之后,深深地變成了他的腦殘粉。本來本著閑著沒事追追星的心態去看STL代碼剖析,不到1/5的內容里,我已經被STL大師深深折服。不愧是大師,代碼寫得面面俱到,宏定義用得如火純青,光是allocator就秒殺只會寫new和delete的5星渣渣。建議和我一樣的C++新手們也去看看,看完就不是新手啦!
轉載于:https://www.cnblogs.com/zhchngzng/articles/4097436.html
總結
以上是生活随笔為你收集整理的一个return引发的血案 - 自己动手实现allocator的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机网络之数据链路层:14、局域网的基
- 下一篇: BZOJ1555 KD之死