[C++]STL-空間配置器(一)
空間配置器
從STL的實現來說,首先需要明白的就是空間配置器,因為整個STL的操作物件都放在容器中,而容器需要一定配置空間以置放資料。
空間配置器的標準介面
// 標準介面,一些typedef
allocator::value_type;
allocator::pointer;
allocator::const_pointer;
allocator::reference;
allocator::const_reference;
allocator::size_type;
allocator::difference_type;
allocator::rebind
// 一個巢狀的class template,class rebind<U>擁有唯一成員other,那是一個typedef,代表allocator<U>
allocator::allocator()
// default constructor
allocator::allocator(const allocator&)
// copy constructor
template <class U>allocator::allocator<const allocator<U>&)
// generic copy constructor
allocator::~allocator()
// default constructor
pointer allocator::address(reference x) const
// 返回物件的地址,等同於&x
const_pointer allocator::address(const_reference x)const
// 返回物件的地址,等同於&x
pointer allocator::allocate(size_type n, const void* = 0)
// 配置空間,足以儲存n個T物件,第二引數是個提示,實現上可能利用它來增進區域性,或完全忽略之
void allocator::deallocate(pinter p, size_type n)
// 歸還之前配置的空間
size_type allocator::max_size() const
// 按返回可成功配置的最大空間
void allocator::constructor(pointer p, const T&x)
// 等同於 new(const void* p ) T(x) 即placement new
void allocator::destroy(pinter p)
// 等同於 p->~T()
設計一個簡單的空間配置器
#ifndef _Test_Alloc_
#define _Test_Alloc_
#include <new> // for placement new
#include <cstddef> // for ptrdiff_t, size_t
#include <cstdlib> // for exit()
#include <climits> // for unit_max
#include <iostream> // for cerr
namespace Test_Alloc {
template <class T>
inline T* _allocate(ptrdiff_t size, T*) {
std::set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0)
{
std::cerr << "out of memory" << std::endl;
}
return tmp;
}
template <class T>
inline void _deallocate(T* buffer) {
::operator delete(buffer);
}
template <class T1, class T2>
inline void _construct(T1* p, const T2& value) {
new (p) T1(value);
}
template <class T>
inline void _destroy(T* ptr) {
ptr->~T();
}
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;
template <class U> struct rebind { typedef allocator<U> other; };
pointer allocate(size_type n, const void* hint = 0) {
return _allocate((difference_type)n, (pointer)0);
}
void deallocate(pointer p, size_type n) {
_deallocate(p);
}
void construct(pointer p, const value_type& value) {
_construct(p, value);
}
void destroy(pointer p) { _destroy(p); }
pointer address(reference x) {
return (pointer)&x;
}
const_pointer const_address(const_reference x) {
return (const_pointer)&x;
}
size_type max_size() const {
return size_type(UINT_MAX/sizeof(T));
}
};
} // end of namespace
#endif
#include <vector>
int main() {
int temp[5]{1, 2, 3, 4, 5};
std::vector<int, Test_Alloc::allocator<int>> iv(temp, temp+5);
for (int i = 0; i < 5; ++i)
{
std::cout << iv[i] << std::endl;
}
return 0;
}
(值得注意的是,在terminal中必須加上 -std=c++11 命令來使用C++11)
SGI STL使用一個專屬的、擁有次層配置能力的,效率優越的特殊配置器,在稍後會提及。事實上,SGI STL仍然提供了一個標準的配置器介面,這個標準介面的配置器名為simple_alloc。
具備次配置力的SGI空間配置器
SGI STL的配置器與眾不同,也與標準規範不同,其名稱是alloc而非allocator,而且不接受任何引數。
vector<int, std::alloc> iv;
在STL中,每個容器都指定了其預設的空間配置器。
1. SGI特殊的空間配置器,std::alloc
一般而言,C++的記憶體配置操作和釋放操作是呼叫new,delete。
class Foo{...};
Foo* pf = new Foo;
delete pf;
new實際包含兩個操作
- 1)呼叫::operator new配置記憶體
- 2)呼叫Foo::Foo()構造物件內容。
delete也包含兩個階段:
- 1)呼叫解構函式
- 2)釋放記憶體
STL allocator把這兩個階段分開,記憶體配置操作和釋放由allocate() 和 deallocate()負責,物件構造和析構由construct()和destroy()負責。
2. 構造和析構工具:construct()和destroy()
以下給出 < stl _ construct.h > 的部分內容。
#include <new> // placement new
template <class T1, class T2>
inline void construct(T1* p, const T2^ value) {
new (p) T1(value);
}
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}
// destroy帶區間範圍的版本
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}
// 判讀元素的數值型別是否有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());
}
// 如果有trivial destructor
template <class ForwardIterator, class T>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __flase_type) {
for ( ; first < last; ++first) {
destroy(&*first);
}
}
// 如果有trivial destructor,那麼就什麼都不做
template <class ForwardIterator, class T>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __true_type) {
}
// 正對char*和wchar_t*的特化版本
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t* ) {}
值得注意的是,上述的constructor()接受一個指標p和一個初值value,該函式的用途是將初值設定到指標所指的空間上,也就是placement new運運算元的作用。
而destroy則是接受指標,呼叫其解構函式。其中接受範圍的版本需要特別注意,當範圍非常大時,不斷呼叫解構函式可能會導致效率的極具降低,所以此時我們需要判斷解構函式是否有必須被呼叫的必要,(__type_traits),由此來區別兩種不同的情況。至於如何判斷解構函式是否為trivial,會在未來的文章中給出解釋。
3. 空間的配置與釋放 std::alloc
上文解釋了記憶體配置後的物件構造和析構過程,現在我們來討論記憶體的配置和釋放。
物件構造前的空間配置和物件析構後的空間釋放,由< stl_alloc.h >負責,SGI對此的設計哲學為:
- 向system heap要求空間。
- 考慮多執行緒狀態。
- 記憶體不足時的應變措施。
- 考慮過多“小型區塊”可能造成的記憶體碎片問題。
(此處排除多執行緒的處理)
考慮到小型區塊所可能造成的記憶體破碎問題,SGI設計了雙層配置器,第一季配置器直接呼叫malloc() 和free()(也就是operator new 和 operator delete), 第二級配置器則視情況採用不同的策略:當配置區塊大於128bytes,視之為“足夠大”,便呼叫第一級配置器。當配置區塊小於128bytes時,視為“過小”,為了降低額外負擔,便採用複雜的memory pool整理方式,而不求助於第一級配置器。整個設計是否開放第二級配置器,取決於__USE_MALLOC是否被定義。
#ifdet __USE_MALLOC
...
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc; // 令alloc為第一級配置器
#else
...
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;// 令alloc為第二級配置器
#endif
尤其注意,alloc並不接受任何template型別引數。
無論alloc被定義為第一級或第二級配置器,SGI還為它再包裝一個介面如下,使配置器的介面能夠符合STL規格:
template<class T, class Alloc>
class simple_alloc {
public:
static T* allocate(size_t n) {
return 0 == n ? 0 : (T*)Alloc::aloocate(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));
}
};
其內部的四個成員函式其實都是單純的轉呼叫,呼叫傳遞給配置器的成員函式,這個介面使配置器的配置單位從bytes轉為個別元素的大小。
SGI STL容器都使用這種介面。
template <class T, class Alloc = alloc>
class vector {
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
void deallocate() {
if (...) {
data_allocator::deallocate(start, end_of_storage - start);
}
}
...
};
一、二級配置器的關係,
介面包裝,以及實際運用方式,見下圖: