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
, 所以例子中都是呼叫的構造析構等都是封裝在該類中.