STL:allocator 空间配置器
Posted 小键233
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL:allocator 空间配置器相关的知识,希望对你有一定的参考价值。
大概…………很久很久以前,我做了一个浪(er)漫(bi)的决定,就是自己也实现一个STL,毕竟造轮子呀,才是最浪漫的不是吗。
于是,山很高,海很深,浪花一浪接一浪,我义无反顾走上了作死之路。
nice ,作死之路的第一部分,就是关于allocator,空间配置器的。
STL 有自己的内存管理,其中最最基础的就是allocator ,任何内存的请求和释放都要经过它。
它本质上就是封装了一下malloc 和free 函数。
对的,它并不是使用 operator new 和operator delete 来分配和回收内存,而使用了c 函数的malloc 和free 。事实上,我也很喜欢malloc 和free 函数:)
在接下去阅读之前,我已经假设,你已经懂得traits 技术(萃取),placement new 、new_handler的应用。如果你对这个一头雾水,那么建议你先了解一下或者看看我以往的博文。traits 技术和 new
准备工作
首先,你可以去下载SGI 版本的STL 源码,我就是依据这个版本的学习的。在它的官网可以下载。
我建议同时下载最新的版本和STL30 的版本。因为最新的版本支持C++11 的标准,但是直接阅读的话并不是那么友好。而STL30 的版本呢直接阅读就清晰明了了。
其次,你在实现的过程中,还可以打开C++ 的官网,查询一下标准是怎么样的,然后就可以根据标准来实现自己的版本。
为了避免命名冲突,可以把代码写入自己自定义的一个命名空间中。
一级配置器
在STL 中,allocator 是分为一级和二级的。一级的就处理超过128bytes 的数据,二级的就处理小于128bytes 的数据。但是二级的空间配置器太复杂了,这里不再详述,但是强烈建议了解一下。
C++ 的标准是,空间配置器要完成内存的分配和构造功能。
但是SGI 中的空间配置器灰常傲娇。
它的allocator 是使用C函数 malloc ,但是C++ 中的new 是要有内存分配和对象构造两部分组成的。单单malloc 是完成不了一个 new 的工作量的,那怎么办呢?
使用placement new。事实上,STL 中的内存分配和对象构造是分开执行的,不同的函数负责执行不同的功能。我觉得很明智,毕竟我不是那么喜欢new。
SGI 的傲娇,还体现在命名上,C++ 的空间配置器的标准名称是allocator ,但是它的叫alloc。
它也做了一些兼容的工作,所以,里面也有符合C++ 标准的版本
这部分的内容在文件stl_alloc.h
中。
alloc
代码如下:
typedef void (*alloc_handler)();
//我也不知道为什么要设为模板,标准库就是这么实现的
//难道是为了以后的扩展?
template<int inst>
class __alloc_template
//处理 out of memeory 的
static void* _xj_oom_malloc(size_t);
static void* _xj_oom_realloc(void*, size_t);
static alloc_handler __alloc_oom_handler; //用来存放处理的指针
public:
static void* allocate(size_t __n)
void* result = malloc(__n);
if(result == 0) result = _xj_oom_malloc(__n);
return result;
static void deallocate(void* p, size_t /* __n */ )
free(p);
//没有做证同测试,比较信任realloc 吧
static void* reallocate(void* p, size_t __n ,size_t __new_size )
void* result = realloc(p, __new_size);
if(0 == result) result = _xj_oom_realloc(p, __new_size);
return result;
static alloc_handler set_alloc_handler ( alloc_handler __new_handler)
alloc_handler temp(__alloc_oom_handler);
__alloc_oom_handler = __new_handler;
return temp;
;
template<int inst>
alloc_handler __alloc_template<inst>::__alloc_oom_handler =0;
template<int inst>
void* __alloc_template<inst>::_xj_oom_malloc(size_t _s)
alloc_handler my_handler;
void* result;
while(true)
my_handler = __alloc_oom_handler;
if(0 == my_handler) __XJ_THROW_BAD_ALLOC;
my_handler();
result = malloc(_s);
if(result) return result;
template<int inst>
void* __alloc_template<inst>::_xj_oom_realloc(void*p, size_t _s)
alloc_handler my_handler;
void* result;
while(true)
my_handler = __alloc_oom_handler;
if(0 == my_handler) __XJ_THROW_BAD_ALLOC;
my_handler();
result = realloc(p, _s);
if(result) return result;
typedef __alloc_template<0> alloc;
它的做法是,实现一个模板类先,然后再typedef 一下,就变成广泛使用的alloc 名称了。正如注释所言,我不知道理由在哪里,唯一能想到的就是为了以后的扩展了。
分配内存使用allocate 函数
回收内存使用deallocate 函数。虽然名义上接受两个参数,但是实际上只需要一个就可以了,因为free 只要一个函数。
它还模拟了分配内存,空间不足时的new_handler 行为。
__alloc_oom_handler 就是用来存储处理的句柄的,但是除非你指定,不然它是空指针的。
reallocate 就是在已有的空间基础上重新分配内存。
allocate 函数行为很明确,如果请求不到内存,那么就丢给_xj_oom_malloc 来处理。而_xj_oom_malloc 中有一个无限循环,不停地调用__alloc_oom_handler 函数(当然这个函数指针必须不为0),试图做一点最后的处理。
如果__alloc_oom_handler 也无能为力,那么只要throw 异常。
其中那个宏实际如下:
#define __XJ_THROW_BAD_ALLOC throw std::bad_alloc()
这就是alloc 的全部代码了。
simple_alloc
但是,alloc 并没有和类型相关,每次分配内存的时候,都要额外计算实际的内存值。
比如说,我要分配一个int ,我希望使用的类似于allocate(1)
这样的,而不是allocate(1*sizeof(int)
。
于是,我们要需要一个类封装一下alloc,如下:
/**
* 这是STL使用的,但这个不可以直接去套用
*/
template<typename T, typename Alloc>
class simple_alloc
public:
static T* allocate(size_t n)
return n!=0 ? (T*) Alloc::allocate(n*sizeof(T) ) :0 ;
static T* allocate()
return (T*) Alloc::allocate(sizeof(T) );
static void deallocate(T* p, size_t n)
Alloc::deallocate(p, n*sizeof(T) );
static void deallocate(T* p)
Alloc::deallocate(p, sizeof(T) );
;
一个值得注意的问题就是申请的内存为0时,怎么处理。
使用的时候实例化simple_alloc 就可以了。
标准的allocator
看一下标准的allocator 是怎么玩的:
/**
* 下面这个是C++ 规定的标准接口,但是SGI 的STL 并不使用这个接口
* 反而使用更简单一点的,这个可以直接套用
*/
template<typename T>
class allocator
typedef alloc _Alloc;
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
allocator()
allocator(const allocator& )
template<typename T1>
allocator(const allocator<T1>& )
~allocator ()
//暂时还不明白这个怎么用
template<typename T1> struct rebind
typedef allocator<T1> other;
;
T* address(T& _x) return &_x;
const T* address(const T& _x) return &_x;
//__n 不能为0,C++ 没有说为什么
//后面那个参数我也不知道是干嘛的
T* allocate(size_t __n, const void* = 0)
return __n != 0 ? static_cast<T*>( _Alloc::allocate(__n * sizeof(T) ) ) : 0;
//p 不能为nullptr
void deallocate(T* p, size_t s)
_Alloc::deallocate(p, s*sizeof(T) );
//size_t (-1) 应该是利用了补码的表现形式
size_t max_size() const
return size_t(-1) / sizeof(T);
//construct he destroy
void construct(T* p, const T& value)
new (p) T(value);
void destroy(T* p) p->~T();
;
template<>
class allocator<void>
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef void* pointer;
typedef const void* const_pointer;
typedef void value_type;
template<typename T> struct rebind
typedef allocator<T> other;
;
;
这个allocator 在G++ 中,可以直接使用:
vector<int, allocator> v;
代码在github 上可以得到。如果中间有变更,不保证博客的代码永远最新。
二级配置器
二级的空间配置器是处理申请小于128bytes 的内存。它使用了内存池的概念。
由于比较复杂,这里不再详述。
但是,二级配置器的一个问题在于,小的内存碎片永远不会被真正free ,还给系统。本来就是为了效率而生,怎么使用倒是看代码的需求吧。
以上是关于STL:allocator 空间配置器的主要内容,如果未能解决你的问题,请参考以下文章