SGI STL空間配置器-第一級空間配置器
一、SGI STL配置器簡介
如果要了解STL的實現,必須要了解空間配置器,因為整個STL的操作物件都放在容器之內,而容器一定需要配置空間以存放資料等資料。allocator叫做空間配置器而不是記憶體配置器,因為空間不一定是記憶體,也可以是磁碟或者其他輔助儲存介質。可以寫一個allocator直接向硬碟取空間。當然這裡介紹的allocator配置的是記憶體。
二、SGI標準的空間配置器
其實SGI也定義了一個符合部分標準,名為allocator的配置器,但是它自己不使用,也不建議我們使用,主要原因是效率不佳。它只是把C++的操作符::operator new和::operator delete做了一層簡單的封裝而已。下面是程式碼,可以看出空間配置器的標準介面,提供了預設的構造器、複製、析構等介面。另外這裡的SGI版本的allocator僅僅對底層的記憶體配置/釋放行為(::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
三、SGI特殊的空間配置器alloc
一般而言,我們所習慣的C++記憶體配置操作和釋放操作是這樣的:
class Foo{...};
Foo* pf = new Foo; //配置記憶體,然後構造物件
delete //將物件析構,釋放記憶體
通常,C++中用new操作符來分配記憶體都包括兩個階段(如上):
(1)呼叫::operator new配置記憶體
(2)呼叫建構函式Foo::Foo()來構造物件內容
同理,delete操作也包括兩個階段:
(1)呼叫解構函式Foo::~Foo()將物件析構
(2)呼叫::operator delete釋放記憶體
為了精密分工,SGI allocator將兩個階段分開:
記憶體配置操作由alloc:allocate負責,記憶體釋放由alloc:deallocate負責;物件構造操作由::contructor()負責,物件析構由::destroy()負責。
配置器定義在標頭檔案中,它裡面又包括兩個檔案:
#include <stl_alloc.h> // 負責記憶體空間的配置和器釋放
#include <stl_construct.h> // 負責物件的構造和析構
下圖顯示了其結構:
1、物件的建構和結構函式construct()和destroy()
下圖顯示了這兩個函式的結構和功能。他們被包含在標頭檔案stl_construct.h中。
函式construct()使用了定位new操作符,其原始碼:
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value); // 定為new操作符placement new; 在指標p所指處構造物件
}
函式destroy則有兩個版本。
第一個版本較簡單,接受一個指標作為引數,直接呼叫物件的解構函式即可,其原始碼:
template <class T>
inline void destroy(T* pointer) {
pointer->~T(); // 呼叫解構函式
}
第二個版本,其引數接受兩個迭代器,將兩個迭代器所指範圍內的所有物件析構掉。而且,它採用了trivial程式設計技法(這裡有介紹http://blog.csdn.net/mmshixing/article/details/51657168):依據元素的型別,判斷其是否有trivial destructor(無用的解構函式)進行不同的處理。這也是為了效率考慮。因為如果每個物件的解構函式都是trivial的,那麼呼叫這些毫無作用的解構函式會對效率造成影響。
下面看其原始碼:
//destroy()第二個版本,接受兩個迭代器。此函式設法找出元素的數值型別
//然後利用__type_trivial<>求取最適當措施。
Template <class ForwardInterator>
Inline void destroy(ForwardInterator first,ForwardInterator last, T*)
{
Typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
__destroy_aux(first, last, trivial_destructor());
}
//下面是對__destroy_aux的過載,第三個引數分別為__true_type、__false_type
//如果元素的數值型別(value type)有non-trivial 函式
Template <class ForwardIterator>
Inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
for( ; first < last ; ++first)
destroy(&*first); //對[first, last)範圍的多有物件析構掉!
}
//如果元素的數值型別(value type)有trivial 函式
Template <class ForwardIterator>
Inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}//什麼也不做
2、空間的配置和釋放,std::alloc
物件構造前的空間分配和析構後的空間釋放,定義在標頭檔案
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裡面。記憶體不足處理函式,由程式猿自己通過new-handler自己設計,在標頭檔案中這樣定義
typedef void (*new_handler)();
這個在《effctive c++》第二版條款7中有詳細說明。
new_handler set_new_handler(new_handler p) throw();
下面列出第一級配置器__malloc_alloc_template程式碼:
#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;
以上為STL空間配置器及第一級配置器__malloc_alloc_template詳細內容,大部分是《STL原始碼剖析》這本書上的,自己捋一下,思路更加清晰。