一、SGI STL配置器簡介
SGI STL的配置器與衆不同,它與標準規範不同。如果要在程序中明確使用SGI配置器,那麼應該這樣寫:
- vector<int,std::alloc> iv;
vector<int,std::alloc> iv;
他的名字是alloc,而且不接受任何參數。標準配置器的名字是allocator,而且可以接受參數。
SGI STL的每一個容器都已經指定了缺省配置器:alloc。我們很少需要自己去指定空間配置器。比如vector容器的聲明:
- template <class T, class Alloc = alloc> // 預設使用alloc爲配置器
- class vector {
- //...
- }
template <class T, class Alloc = alloc> // 預設使用alloc爲配置器
class vector {
//...
}
其實SGI也定義了一個符合部分標準,名爲allocator的配置器,但是它自己不使用,也不建議我們使用,主要原因是效率不佳。它只是把C++的操作符::operator new和::operator delete做了一層簡單的封裝而已。下面僅僅貼出代碼:
- #ifndef DEFALLOC_H
- #define DEFALLOC_H
- #include <new.h>
- #include <stddef.h>
- #include <stdlib.h>
- #include <limits.h>
- #include <iostream.h>
- #include <algobase.h>
- template <class T>
- inline T* allocate(ptrdiff_t size, T*) {
- set_new_handler(0);
- T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
- if (tmp == 0) {
- cerr << "out of memory" << endl;
- exit(1);
- }
- return tmp;
- }
- template <class T>
- inline void deallocate(T* buffer) {
- ::operator delete(buffer);
- }
- template <class T>
- class allocator {
- 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;
- pointer allocate(size_type n) {
- return ::allocate((difference_type)n, (pointer)0);
- }
- void deallocate(pointer p) { ::deallocate(p); }
- pointer address(reference x) { return (pointer)&x; }
- const_pointer const_address(const_reference x) {
- return (const_pointer)&x;
- }
- size_type init_page_size() {
- return max(size_type(1), size_type(4096/sizeof(T)));
- }
- size_type max_size() const {
- return max(size_type(1), size_type(UINT_MAX/sizeof(T)));
- }
- };
- class allocator<void> {
- public:
- typedef void* pointer;
- };
- #endif
#ifndef DEFALLOC_H
#define DEFALLOC_H
#include <new.h>
#include <stddef.h>
#include <stdlib.h>
#include <limits.h>
#include <iostream.h>
#include <algobase.h>
template <class T>
inline T* allocate(ptrdiff_t size, T*) {
set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0) {
cerr << "out of memory" << endl;
exit(1);
}
return tmp;
}
template <class T>
inline void deallocate(T* buffer) {
::operator delete(buffer);
}
template <class T>
class allocator {
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;
pointer allocate(size_type n) {
return ::allocate((difference_type)n, (pointer)0);
}
void deallocate(pointer p) { ::deallocate(p); }
pointer address(reference x) { return (pointer)&x; }
const_pointer const_address(const_reference x) {
return (const_pointer)&x;
}
size_type init_page_size() {
return max(size_type(1), size_type(4096/sizeof(T)));
}
size_type max_size() const {
return max(size_type(1), size_type(UINT_MAX/sizeof(T)));
}
};
class allocator<void> {
public:
typedef void* pointer;
};
#endif
三、SGI特殊的空間配置器alloc
通常,C++中用new操作符來分配內存都包括兩個階段:
(1)調用::operator new配置內存
(2)調用構造函數來構造對象內容
同理,delete操作也包括兩個階段:
(1)調用析構函數將對象析構
(2)調用::operator delete釋放內存
爲了精密分工,SGI allocator將兩個階段分開:
內存配置操作由alloc:allocate負責,內存釋放由alloc:deallocate負責;對象構造操作由::contructor()負責,對象析構由::destroy()負責。
配置器定義在頭文件<memory>中,它裏面又包括兩個文件:
- #include <stl_alloc.h> // 負責內存空間的配置和器釋放
- #include <stl_construct.h> // 負責對象的構造和析構
#include <stl_alloc.h> // 負責內存空間的配置和器釋放
#include <stl_construct.h> // 負責對象的構造和析構
下圖顯示了其結構:
1、對象的建構和結構函數construct()和destroy()
圖二顯示了這兩個函數的結構和功能。他們被包含在頭文件stl_construct.h中。
圖二 函數construct()和destroy()示意圖
函數construct()使用了定位new操作符,其源代碼:
- template <class T1, class T2>
- inline void construct(T1* p, const T2& value) {
- new (p) T1(value); // 定爲new操作符placement new; 在指針p所指處構造對象
- }
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value); // 定爲new操作符placement new; 在指針p所指處構造對象
}
第一個版本較簡單,接受一個指針作爲參數,直接調用對象的析構函數即可,其源代碼:
- template <class T>
- inline void destroy(T* pointer) {
- pointer->~T(); // 調用析構函數
- }
template <class T>
inline void destroy(T* pointer) {
pointer->~T(); // 調用析構函數
}
第二個版本,其參數接受兩個迭代器,將兩個迭代器所指範圍內的所有對象析構掉。而且,它採用了一種特別的技術:依據元素的型別,判斷其是否有trivial destructor(無用的析構函數)進行不同的處理。這也是爲了效率考慮。因爲如果每個對象的析構函數都是trivial的,那麼調用這些毫無作用的析構函數會對效率造成影響。
下面看其源代碼:
- // 如果元素的數值型別(value type)有 non-trivial destructor…
- template <class ForwardIterator>
- inline void
- __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
- for ( ; first < last; ++first)
- destroy(&*first);//調用析構函數
- }
- // 如果元素的數值型別(value type)有 trivial destructor…
- template <class ForwardIterator>
- inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}//不調用析構函數
- // 判斷元素的數值型別(value type)是否有 trivial destructor,分別調用上面的函數進行不同的處理
- template <class ForwardIterator, class T>
- inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
- typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
- __destroy_aux(first, last, trivial_destructor());
- }
- // 以下是 destroy() 第二版本,接受兩個迭代器。它會設法找出元素的數值型別,
- // 進而利用 __type_traits<> 求取最適當措施。
- template <class ForwardIterator>
- inline void destroy(ForwardIterator first, ForwardIterator last) {
- __destroy(first, last, value_type(first));
- }
// 如果元素的數值型別(value type)有 non-trivial destructor…
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
for ( ; first < last; ++first)
destroy(&*first);//調用析構函數
}
// 如果元素的數值型別(value type)有 trivial destructor…
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}//不調用析構函數
// 判斷元素的數值型別(value type)是否有 trivial destructor,分別調用上面的函數進行不同的處理
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
__destroy_aux(first, last, trivial_destructor());
}
// 以下是 destroy() 第二版本,接受兩個迭代器。它會設法找出元素的數值型別,
// 進而利用 __type_traits<> 求取最適當措施。
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}
第二版本還針對迭代器爲char*和wchar_t*定義了特化版本:
- inline void destroy(char*, char*) {}
- inline void destroy(wchar_t*, wchar_t*) {}
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}
對象構造前的空間分配和析構後的空間釋放,定義在頭文件<stl_alloc.h>中。其設計思想是:
- 向system heap要求空間。
- 考慮多線程狀態。
- 考慮內存不足時的應變措施。
- 考慮過多“小額區塊”可能造成的內存碎片問題。
考慮到小型區塊可能造成的內存破碎問題,SGI設計了雙層級配置器,第一級配置器直接使用malloc()和free(),第二級則視情況採用不同的策略。而且採用了複雜的內存池memory pool整理方式。整個設計究竟是隻開放第一級配置器還是同事開放第二級配置器取決於宏__USE_MALLOC是否被定義。
- # ifdef __USE_MALLOC
- ...
- typedef __malloc_alloc_template<0> malloc_alloc; <span style="font-family: KaiTi_GB2312;">//令 alloc爲第一級配置器</span>
- typedef malloc_alloc alloc;
- # else
- ...
- //令 alloc 爲第二級配置器
- typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
- #endif /* ! __USE_MALLOC */
# ifdef __USE_MALLOC
...
typedef __malloc_alloc_template<0> malloc_alloc; <span style="font-family:KaiTi_GB2312;">//令 alloc爲第一級配置器</span>
typedef malloc_alloc alloc;
# else
...
//令 alloc 爲第二級配置器
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
#endif /* ! __USE_MALLOC */
SGI並未定義它。
無論alloc被定義爲第一級或者是第二級配置器,SGI還爲它包裝一個接口如下,使配置器的接口能夠符合STL規格:
- template<class T, class Alloc>
- class simple_alloc {
- public:
- static T *allocate(size_t n)
- { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
- static T *allocate(void)
- { return (T*) 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)); }
- };
template<class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n)
{ return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
static T *allocate(void)
{ return (T*) 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)); }
};
SGI STL容器全部是使用這個simple_alloc接口。第一級和第二級配置器之間的關係如圖三所示。
圖三 第一級配置器和第二級配置器
第一級和第二級配置器的包裝接口和運用方式如下:
第一級配置器__malloc_alloc_template剖析
第一級配置器直接使用malloc(),free(),realloc()等C函數執行實際的內存配置、釋放、重配置操作,並實現出類似C++ new handler機制。它有獨特的out-of-memory內存處理機制:在拋出std::bad_alloc異常之前,調用內存不足處理例程嘗試釋放空間,如果用戶沒有定義相應的內存不足處理例程,那麼還是會拋出異常。詳細實現見函數oom_malloc(),oom_realloc()。
內存不足處理例程保存在函數指針__malloc_alloc_oom_handler裏面。
下面列出代碼:
- #if 0
- # include <new>
- # define __THROW_BAD_ALLOC throw bad_alloc
- #elif !defined(__THROW_BAD_ALLOC)
- # include <iostream.h>
- # define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
- #endif
- // malloc-based allocator. 通常比稍後介紹的 default alloc 速度慢,
- //一般而言是 thread-safe,並且對於空間的運用比較高效(efficient)。
- //以下是第一級配置器。
- //注意,無「template 型別參數」。至於「非型別參數」inst,完全沒派上用場。
- template <int inst>
- class __malloc_alloc_template {
- private:
- //以下都是函式指標,所代表的函式將用來處理內存不足的情況。
- // oom : out of memory.
- static void *oom_malloc(size_t);
- static void *oom_realloc(void *, size_t);
- static void (* __malloc_alloc_oom_handler)();
- public:
- static void * allocate(size_t n)
- {
- void *result =malloc(n);//第一級配置器直接使用 malloc()
- // 以下,無法滿足需求時,改用 oom_malloc()
- if (0 == result) result = oom_malloc(n);
- return result;
- }
- static void deallocate(void *p, size_t /* n */)
- {
- free(p); //第一級配置器直接使用 free()
- }
- static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
- {
- void * result =realloc(p, new_sz);//第一級配置器直接使用 rea
- // 以下,無法滿足需求時,改用 oom_realloc()
- if (0 == result) result = oom_realloc(p, new_sz);
- return result;
- }
- //以下模擬 C++的 set_new_handler(). 換句話說,你可以透過它,
- //指定你自己的 out-of-memory handler
- static void (* set_malloc_handler(void (*f)()))()
- {
- void (* old)() = __malloc_alloc_oom_handler;
- __malloc_alloc_oom_handler = f;
- return(old);
- }
- };
- // malloc_alloc out-of-memory handling
- //初值爲 0。有待客端設定。
- template <int inst>
- void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
- template <int inst>
- void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
- {
- void (* my_malloc_handler)();
- void *result;
- for (;;) {
- //不斷嘗試釋放、配置、再釋放、再配置…
- my_malloc_handler = __malloc_alloc_oom_handler;
- if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
- (*my_malloc_handler)();//呼叫處理例程,企圖釋放內存。
- result = malloc(n); //再次嘗試配置內存。
- if (result) return(result);
- }
- }
- template <int inst>
- void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
- {
- void (* my_malloc_handler)();
- void *result;
- for (;;) { //不斷嘗試釋放、配置、再釋放、再配置…
- my_malloc_handler = __malloc_alloc_oom_handler;
- if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
- (*my_malloc_handler)();//呼叫處理例程,企圖釋放內存。
- result = realloc(p, n);//再次嘗試配置內存。
- if (result) return(result);
- }
- }
- //注意,以下直接將參數 inst指定爲 0。
- typedef __malloc_alloc_template<0> malloc_alloc;