1. 程式人生 > >STL原始碼分析之空間配置器

STL原始碼分析之空間配置器

前言

SGI STL將new的申請空間和呼叫建構函式的兩個功能分開實現, 如果對new不太清楚的, 可以先去看看這一篇new實現再來看配置器也不遲. 本節是STL分析的第一篇, 主要分析STL各個部分都會出現的alloc實現, 雖然每個部分都只會預設呼叫它, 不瞭解它也可以看懂分析, 但是他又是不可缺少的, 我們就以它做為開篇進行分析.

"new"的實現

這裡直接我們直接來看STL的construct實現吧

// 這裡的construct呼叫的是placement new, 在一個已經獲得的記憶體裡建立一個物件
template <class T1, class T2>
inline void construct(T1* p, const T2& value) { new (p) T1(value); }

可以明白這裡就只是一個placement new的呼叫, 只是用了泛型來實現一個物件分配的模板, 並實現初始化.

既然已經看到了物件的分配, 那再接再厲看看空間的分配, 充分了解STL是怎麼將new分開執行的. allocate函式實現空間的申請, 但是這裡有一點看不出來, 申請記憶體是有分為一級配置器和二級配置器, 分配的空間小於128位元組的就呼叫二級配置器, 大於就直接使用一級配置器, 一級配置器直接呼叫malloc申請, 二級使用記憶體池.

template<class T>
inline T* allocate(ptrdiff_t size, T*)
{
    set_new_handler(0);
    T* tmp = (T*)(::operator new(size)(size * sizeof(T)));
    if(!tmp)
    {
        cerr << "out of memort" << endl;
        exit(1);
    }
    return tmp;
}

記憶體分配果然是呼叫operator new來執行空間分配, 這裡allocate和construct都只是簡單的對operator new

, placement new進行封裝.

const int N = 4;
int main()
{
	allocator<string>  alloc;
	auto str_ve = alloc.allocate(N);
	auto p = str_ve;	// vector<string> *p = str_ve;
	alloc.construct(p++);
	alloc.construct(p++, 10, 'a');
	alloc.construct(p++, "construct");
    cout << str_ve[0] << endl;
	cout << str_ve[1] << endl;
	cout << str_ve[2] << endl;
	
	while(p != str_ve)
	{
		alloc.destroy(--p);
	}
	alloc.deallocate(str_ve, N);

	exit(0);
}

輸出結果為

[email protected]:stl3.0$ ./a.out 

aaaaaaaaaa
construct

這個程式首先呼叫allocate申請N個大小的空間, 在依次construct呼叫建構函式, 這裡就先初始化3個結構, 緊接著通過destory呼叫解構函式, 最後deallocate釋放申請的空間. 整個過程很容易理解, 但是這裡還要深入是dealllocate和destroy兩個函式.

"delete"實現

先是看destroy呼叫解構函式. 而destroy有兩個版本.

版本一:

需要傳入的引數 : 一個指標

// 第一版本, 接收指標
template <class T> inline void destroy(T* pointer) 
{
    pointer->~T();
}

版本一直接就呼叫了解構函式, 不用過多的分析.

版本二:

需要傳入的引數 : 兩個迭代器

// 第二個版本的, 接受兩個迭代器, 並設法找出元素的型別. 通過__type_trais<> 找出最佳措施
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) 
{
  __destroy(first, last, value_type(first));
}

// 接受兩個迭代器, 以__type_trais<> 判斷是否有traival 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直接呼叫__destroy, 前者只是一個介面, 所以重點是在後者.

分析__type_traits<> : 它是用於獲取迭代器所指物件的型別,運用traits技法實現的.只要記住我們用來獲取對物件型別就可以了. 然後通過型別的不一樣選擇執行不同的析構呼叫.

__type_traits__false_type時, 呼叫的是下面這個函式, 通過迭代所有的物件並呼叫版本一的函式執行解構函式進行析構. 而這個是被稱為non-travial destructor

// 沒有non-travial destructor 
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) 
{
  for ( ; first < last; ++first)
    destroy(&*first);
}

__type_traits__true_type時, 什麼也不做, 因為這樣效率很高效, 並不需要執行解構函式. 而這個是被稱為travial destructor.

// 有travial destructor
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

最後是版本二的特化版, 同樣也什麼都不用做, 沒有必要做析構.

inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

destroy分為這麼幾個版本和幾個不同的函式執行都是為了提升效率, 較小的呼叫並不能看出什麼, 但是如果是範圍析構的話這樣不同的選擇析構能很節約時間和效率.

講解完了destory後應該就能明白上面程式碼迴圈執行析構函數了.


小結

這裡用一個小小的例子來理解"new"和"delete"運算子, 理解new, delete每步分開執行, 記憶體釋放(deallocate)這裡沒有講解, 也只是簡單的呼叫free函式. STL這樣做1. 為了效率, 2. 為了構建記憶體池.

最後將所有的函式進行封裝到allocator , 所以例子中都是呼叫的構造析構等都是封裝在該類中.