先解決一個坑 allocator
在源碼剖析的第二章,便是講的allocator,(第一次看的時候一臉蒙B)
因爲所有的STL的操作對象都存放在容器內,而容器一定要配置空間以置放資料,都要用到allocator
SGI標準的空間適配器 std::allocator
下面的代碼,只是把C++的:::operator new
和 ::operator delete
做一層薄薄的包裝,SGI 從未使用過它,也不建議我們使用,主要原因是效率不佳
//這個程序在CODEBLOCKS上運行失敗,有很多錯誤
#ifndef DEFALLOC_H
#endif DEFALLOC_H
#include <new.h> //for placement new
#include <stddef.h> //for ptrdiff_t, size_t
#include <stdlib.h> //for exit()
#include <iostream.h> //for cerr
#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) //(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));
//UINT_MAX沒有初值啊?
}
};
#endif
SGI特殊的空間配置器 std::alloc
class Foo {....};
Foo* pf = new Foo; //配置內存,然後構造對象
delete pf; //將對象析構,然後釋放內存
new內含倆階段操作:
1.調用::operator new
配置內存
2.調用Foo::Foo()
構造對象內容
delete也是倆階段操作:
1.調用Foo::~Foo()
將對象析構
2.調用 ::operator delete
釋放內存
爲了精密分工,STL allocator決定將這倆階段操作區分開來
內存配置操作由alloc:allocator()
負責
內存釋放操作由alloc:deallocate()
負責
對象構造操作由 ::construct()
負責
對象析構操作由 ::destroy()
負責
STL allocator的整體結構
構造和析構基本工具: construct()
和 destroy()
#include<new.h> //for placement new
template<class T1, class T2>
inline void construct(T1*p, const T2& value)
{
new (p) T1(value); //placement new; 調用 T1::T1(value);
}
//以下是destroy()第一個版本,接受一個指針
template<class T>
inline void destroy(T* pointer)
{
pointer->~T(); //調用dtor ~T()
}
//以下是destroy()的二個版本,接受倆個迭代器,此函數設法找出元素的數值型別
//進而利用 _type_traits<>求取最適當措施
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last)
{
_destroy(first,last,value_type(first));
}
//判斷元素的數值型別(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());
}
//如果元素的數值型別(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) {} //什麼都不做
//以下是detroy()第二版本針對迭代器爲char* 和 wchar_t* 的特化版
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}
這幅圖片很好的概括了以上代碼
這裏再說一下上面代碼幾個點:
1.trivial destructor
has_trivial_destructor
函數
trivial的意思是”不重要的,無意義的“的,所以non-trivial就是”有意義的“
現在膚淺的理解是:如果不定義析構函數,用系統自帶的,則是trivial distructor,什麼都不用做。但如果是non_trivial distructor的話,說明用戶自定義了析構函數(問題來了。那麼不應該直接調用它定義的析構函數嗎?)
STL源碼剖析中有這麼一段話:(P53)
第二版本destroy,接受first,last倆個迭代器,將[first, last)範圍內的所有對象析構掉,我們不知道這個範圍多大,萬一很大,而每個對象的析構函數都無關痛癢(所謂trivial destructor),那麼一次次調用這些無關痛癢的析構函數對效率是一種傷害
因此,首先利用 value_type() 獲得迭代器所指對象的型別,再利用 _type_traits<T> 判斷該型別的析構函數是否無關痛癢。若是(_true_type),則什麼也不做就結束;若否(_false_type),這才以循環方式巡防整個範圍,並在循環中每經歷一個對象就調用第一個版本的destroy()
空間的配置與釋放 std::alloc
上面說了對象的構造和析構
然後再說空間(內存)的配置和釋放
內存配置操作由alloc:allocator()
負責
內存釋放操作由alloc:deallocate()
負責
看到很有趣的一短話:
SGI對此的設計哲學如下:
- 向 system heap 要求空間
- 考慮多線程 (multi-threads)狀態 (一下內容皆不考慮)
- 考慮內存不足時的應變措施
- 考慮過多“小型區塊”可能造成的內存碎片(fragment)問題
接下來的代碼都是根據這幾點設計的
2016.5.4 有時間繼續補吧