您的位置:首页 > 其它

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
对应的也应该有两种,一种是第一级配置器,另一种是第二级配置器。在下节中,我们将看看第一级配置器是如何分配以及释放内存的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: