STL源码剖析——空间配置器Allocator#1 构造与析构
以STL的运用角度而言,空间配置器是最不需要介绍的东西,因为它扮演的是幕后的角色,隐藏在一切容器的背后默默工作。但以STL的实现角度而言,最应该首先介绍的就是空间配置器,因为这是这是容器展开一切运作的基石。空间配置器顾名思义就是配置空间的器件,为存放在容器里的信息找到安家落户的地方(内存)。
SGI STL上有两个空间配置器,一个是std::allocator,一个是std::alloc,前者只是单纯的把基层的内存配置/释放行为(::operator new 和 ::operator delete)做了一层简单的封装,不作总结;后者才是我们应该重点学习的对象。
一般而言,C++的内存配置与释放操作是这样的:
1 class Foo {...}; 2 Foo* pf = new Foo; 3 delete pf;
其中的new包含两阶段的操作:(1)调用::operator new分配内存;(2)调用Foo::Foo()构造对象内容。delete也包含两阶段的操作:(1)调用Foo::~Foo()将对象析构;(2)调用::operator delete释放内存。。
而在std::alloc中同理,内存配置操作由alloc::allocate()负责,释放由alloc::deallocate()负责;对象构造操作由::construct()负责,析构由::destroy()负责。而这两组函数分别在两个头文件里,而这两个头文件又被<memory>所包含:
- #include<stl_alloc.h> //负责内存空间的配置与释放
- #incluce<stl_construct.h> //负责对象内容的构造与析构
从这幅图中能大致看出空间配置器的概貌,本文先就从<stl_construct.h>出发学习空间配置器中对象的构造与析构。
1 //<stl_construct.h>的部分内容 2 3 #include <new.h> // 欲使用 placement new,需先含包含此文件 4 5 template <class T1, class T2> 6 inline void construct(T1* p, const T2& value) { 7 new (p) T1(value); // placement new; 喚起T1::T1(value); 8 } 9 10 /*new (p) T1(value); 是指在p所指的内存上调用T1的构造函数,把其产生的对象存放在p所指内存里。而T1::T1(value);说明T1构造函数的形参为const T2 &(一个T2类型的常引用变量)。*/ 11 12 // 以下是 destroy() 第一版本,接受一個指標。 13 template <class T> 14 inline void destroy(T* pointer) { 15 pointer->~T(); // 喚起 dtor ~T() 16 } 17 18 // 以下是 destroy() 第二版本,接受兩個迭代器。此函式是設法找出元素的數值型別, 19 // 進而利用 __type_traits<> 求取最適當措施。 20 template <class ForwardIterator> 21 inline void destroy(ForwardIterator first, ForwardIterator last) { 22 __destroy(first, last, value_type(first)); 23 } 24 25 // 判斷元素的數值型別(value type)是否有 trivial destructor 26 template <class ForwardIterator, class T> 27 inline void __destroy(ForwardIterator first, ForwardIterator last, T*) { 28 typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; 29 __destroy_aux(first, last, trivial_destructor()); 30 } 31 32 // 如果元素的數值型別(value type)有 non-trivial destructor… 33 template <class ForwardIterator> 34 inline void 35 __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) { 36 for ( ; first < last; ++first) 37 destroy(&*first); 38 } 39 40 // 如果元素的數值型別(value type)有 trivial destructor… 41 template <class ForwardIterator> 42 inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {} 43 44 // 以下是destroy() 第二版本針對迭代器為 char* 和 wchar_t* 的特化版 45 inline void destroy(char*, char*) {} 46 inline void destroy(wchar_t*, wchar_t*) {}
construct()接受一个指针p和一个初值value,该函数的作用就是将value作为实参在指针所指的空间上调用构造函数产生对象。关于placement new相关知识可以浏览这篇博客。
destroy()有两个版本,第一个版本很简单,直接调用该对象的析构函数即可。而第二个版本就稍微复杂点,要把[first, last)范围内的所有对象析构掉,这里涉及到一个问题,如果范围很大,但对每个对象的析构函数都无关痛痒,那么一次次调用这些无关痛痒的析构函数会造成性能上的损失。因此,这里首先利用value_type()获得迭代器所指对象的类型,再利用__type_traits<T>判断该类型的析构函数是否无关痛痒。若是(__true_type),则什么也不做;若否(__false_type),这才以循环的方式遍历整个区间范围,并逐一调用每一个对象上析构函数。而对于如何实现获得迭代器所指之物的类型和如何实现判断析构函数是否无关痛痒将在学习trait编程时有所提及。