SGISTL源码探究-空间配置器
2017-09-06 15:32
218 查看
前言
该系列的博客主要是看SGISTL3.0源码,并参考了一些相关的博客还有《STL源码剖析》一书得到的心得及一些自己的理解整理而成,如有错误请指出,谢谢。
allocator
引入
事实上,我们在学习C++的时候,接触到空间配置器应该就是allocator,它用于申请一块未初始化的空间,这是因为
new会执行两个动作,首先申请空间,其次调用其构造函数。但是如果有些空间根本就不会用到,那么调用构造函数初始化会浪费效率。而
allocator解决了这个问题,即分配/释放内存时只调用
malloc和
free,而将构造/析构动作分开。(其实
new和
free一般也是调用这两个函数,但是又额外增加了初始化以及析构动作)
代码实例
#include <iostream> using namespace std; #include <memory> #include <vector> int main() { //定义一个配置器对象 allocator<vector<int> > alloc; //使用该配置器分配3个未初始化的vector<int> vector<int>* p = alloc.allocate(3); vector<int>* q = p; for(int i = 0; i < 3; i++) { //调用vector<int>的构造函数 alloc.construct(q++, 1, i*10); //输出(此时相当于是二维数组p[3][]) cout << p[i][0] << endl; } //循环调用析构函数 while(q != p) { alloc.destroy(--q); } //释放分配的空间 alloc.deallocate(p, 3); return 0; }
编译链接
g++ -std=c++11 allocator.cpp
以上便是
allocator空间配置器最简单的使用。从中我们可以看出要使用
allocator配置器,需要包含头文件
<memory>。以及有如下几个基本的接口:
allocate(size_t n):用于分配n个对象
construct(T* p, args):用于构造类型为T*的指针p指向的对象,
args是构造函数的参数
destroy(T* p):即对类型为T*的指针p指向的对象调用对应的析构函数
deallocate(T* p, size_t n):用于释放内存,需要注意指针
p指向的一定要是分配时的起始地址,否则可能造成未定义的结果。
深入源码
下面我们就来看看allocator的那些接口是如何实现的,首先我们还是先从
construct()和
destroy()入手,因为
allocate()稍微复杂一些,涉及了两级配置器,第一级用于申请超过128bytes的空间,第二级用于申请小空间,采用了内存池技术。
construct()
template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p) T1(value); }
代码其实就一行
new (p) T1(value),但是
new不是会分配新的内存吗,你可能会有这样的疑惑。其实这里的
new跟你认识的
operator new不太一样,而是
placement new,它重载了
operator new,用于在一个已经分配好的内存中(堆/栈)构造一个新的对象。这也是一种显示调用构造/析构函数的一种方法,另一种直接在函数前加作用域。那么这样我相信你就可以理解了。(ps:使用
placement new需要包含头文件
<new.h>)
destroy
destroy其实有多个版本,针对了不同的情况。
传入一个指针
传入两个迭代器,有trivial destructor
传入两个迭代器,有non-trivial destructor
迭代器是char *和wchar *
情况一:
template<class T> inline void destroy(T* pointer) { pointer->~T(); }
这种情况没什么好说的
情况二和情况三的共同部分:
在具体到情况二和三之前,有一些公共的内部接口
template <class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(first, last, value_type(first)); } 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,
value_type()用于获取迭代器所指对象的类型,运用traits技法实现,至于具体如何实,在分析迭代器时会讲解。而在
__destroy中,内部调用
__destroy_aux,最后一个参数是
trivial_destructor(),其实这是一个
typedef,跟
__type_traits有关,也放在后面讲解,现在你只用知道它的作用是判断是否有trivial destructor就行了。
情况二:
template <class ForwardIterator> inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) { for(; first < last; ++first) { destroy(&*first); } }
这个情况是针对non-trivial destructor,意思也就是在[first,last)范围内,调用情况一的函数,即依次调用析构函数。看第三个参数
__false_type,很明显,它是一个对象,嗯….从中你可能会看出点什么。
情况三:
template <class ForwardIterator> inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type){}
这里唯一可以让我们关注的就只有
__true_type了,跟情况二结合不难发现,通过
trivial_destructor()的返回值是
__true_type还是
__false_type,而选择不同的情况。而
trivial_destructor其实是个
typedef定义的,根据不同的情况,分别为
__true_type和
__false_type,比如基础类型int之类的,肯定就typedef成
__true_type了。
__true_type和
__false_type是两个空类。
trivial_destructor()即构造一个对象。
情况四:
inline void destroy(char *, char *){} inline void destroy(wchar_t *, wchar_t *){}
如你所见,这两种类型根本不需要做什么。
分了这几种情况,主要是在效率方面上的考虑,有一些对象本来的析构函数就属于没什么用的,调用这种析构函数只会浪费时间,特别是在范围析构的时候。
小结
在本小节中,我们主要了解了allocator的简单的用法以及
construct和
destroy函数针对不同的情况采取的最具效率的做法。前面已经提到过分为两级配置器,所以
allocate和
deallocate对应的也应该有两种,一种是第一级配置器,另一种是第二级配置器。在下节中,我们将看看第一级配置器是如何分配以及释放内存的。
相关文章推荐
- SGISTL源码探究-默认使用的配置器
- SGISTL源码探究-STL中的hashtable(上)
- SGISTL源码探究-stl_algo.h中的排列算法
- SGISTL源码探究-迭代器的类型
- SGISTL源码探究-stack配接器
- SGISTL源码探究-stl_algo.h中的排序算法
- SGISTL源码探究-queue配接器
- SGISTL源码探究-STL中的红黑树(下)
- SGISTL源码探究-第一级配置器
- SGISTL源码探究-关联式容器:hash_multiset
- SGISTL源码探究-stl_numeric.h中的数值算法
- SGISTL源码探究-STL中的红黑树(上)
- SGISTL源码探究-stl_algobase.h中的算法
- SGISTL源码探究-关联式容器:hash_multimap
- SGISTL源码探究-内存池
- SGISTL源码探究-pair的实现
- SGISTL源码探究-STL中的hashtable(下)
- SGISTL源码探究-stl_algo.h中的基础算法
- SGISTL源码探究-vector容器(下)
- SGISTL源码探究-list容器(上)