STL学习笔记--2空间配置器 allocator
Posted chengyu779394084
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL学习笔记--2空间配置器 allocator相关的知识,希望对你有一定的参考价值。
2.1标准接口
allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
allocator::rebind
allocator::allocator()//默认构造函数
allocator::allocator(const allocator&)//拷贝构造函数
template <class U> allocator::alloctor(const alloctor<U> &)//泛化的拷贝构造
allocator::~allocator()//析构函数
pointer allocator::address(reference x) const //返回某个对象的地址
const_pointer allocator::address(const_reference x) const //返回某个const对象的地址
pointer allocator::allocate(size_type n,const void*=0)//配置空间,存储n个T对象。第二个参数是提示,可忽略
void allocator::deallocate(pointer p,size_type n)//归还先前配置的空间
size_type allocator::max_size() const //返回可配置的最大量
void allocator::construct(pointer p,const T&)//构造T对象
void allocator::destroy(pointer p)//对象T的析构
一个简单的空间配置器 JJ::allocator
#ifndef _JJALLOC_
#define _JJALLOC_
#include <new>
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>
namespace JJ
{
// 使用operator new分配空间
template<class T>
inline T* _allocate(ptrdiff_t size, T*)
{
std::set_new_handler(0);//注释1
T *tmp = (T*)(::operator new((size_t)(size * sizeof(T))));//注释2
if (tmp == 0)
{
std::cerr << "out of memory" << std::endl;
exit(1);
}
return tmp;
}
// 使用operator delete回收空间
template<class T>
inline void _deallocate(T* buffer)
{
::operator delete(buffer);
}
// 在指定内存上构造一个对象
template<class T1, class T2>
inline void _construct(T1* p, const T2& value)
{
// placement new
new (p) T1(value);
}
// 析构一个对象
template<class T>
inline void _destroy(T* ptr)
{
ptr->~T();
}
// 遵循allocator的标准定义相关结构
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 T& 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));
}
};
}
#endif
注释1:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler new_p) throw();
使用set_new_handler
可以设置一个函数new_p,当使用new/operator new分配内存失败时,new_p将被调用。new_p将尝试使得更多内存空间可用,以使得接下来的内存分配操作能够成功。如果new_p指向NULL(默认就是NULL),那么将会抛出bad_alloc异常,这也是为什么我们默认使用new失败的时候将会抛出bad_alloc异常的原因;
注释2:
我们使用的new叫做new operator,包括两个步骤:
一是调用operator new来分配指定大小的内存空间
二是调用构造函数;
所以如果只是进行空间分配操作,那么使用operator new就可以了,就好比C的malloc函数;
如果已经分配好了空间,想在上面构造一个对象,那么可以使用placement new,上面的_construct函数里面调用的就是placement new;
2.2具备次配置能力的SGI空间配置器
SGI的空间配置器名称为alloc,而非allocator,不接受任何参数。
vector<int,allocator<int>> iv;
vector<int,alloc> iv;
我们所习惯的c++内存配置操作和释放操作如下:
class Foo { ... };
Foo* pf = new Foo; // 配置内存,然后构造对象
delete pf; // 将对象析构,然后释放内存
这其中的new 操作符(new operator)包含两阶段操作:
(1)调用operator new配置内存
(2)调用Foo::Foo( )构造函数构造对象内容。
delete操作符包含两阶段操作:
(1)调用Foo::~Foo( )析构函数将对象析构。
(2)调用operator delete释放内存
STL allocator 将这两阶段操作区分开来。
内存配置操作由 alloc::allocate() 负责,内存释放操作由 alloc::deallocate() 负责;
对象构造操作由 ::construct() 负责,对象析构操作由 ::destroy() 负责。
配置器定义在<memory>
中,SGI中
//负责内存空间的配置与释放;
<stl_alloc.h>//文件中定义了一、二两级配置器,彼此合作,配置器名为alloc。
//负责对象内容的配置与释放
<stl_construct.h>//全局函数construct()和destroy(),负责对象的构造和析构。
//用来填充fill或复制copy大块内存数据
<stl_uninitialized.h>//uninitialized_copy();uninitialized_fill();uninitialized_fill_n
uninitialized_copy(first, last, result) //将[first,last)范围内的对象复制到result处;
uninitiated_fill(first, last, X) //将[first,last)范围内的内存用对象X的副本填充;
uninitiated_fill_n(first, n, X) //将first开始的n个连续的内存空间用X的副本填充;
1、构造和析构基本工具:construct()和destroy()
#include <stl_construct.h>
construct()接受一个指针p和初值value,该函数将初值设定到所指空间上;
destroy()有两个版本:
1、接收一个指针,准备将该指针所指之物析构掉。直接调用该对象的析构函数;
2、接收first和last两个迭代器,准备将[first,last)范围内的所有对象析构掉。
利用value_type()获取迭代器所指对象的类型,利用__type_traits<T>判断该类型的析构函数是否无关痛痒。
若是__true_type,则什么也不做,反之若是__false_type则循环访问,对每个对象调用析构函数。
2、空间的配置和释放:std::alloc
#include <stl_alloc.h>
改进:
1.通过内存池技术提升了分配效率:
2.对小内存频繁分配所可能造成的内存碎片问题的处理
3.对内存不足时的处理
SGI STL在<stl_alloc.h>中定义了两级配置器。
第一级空间配置器使用malloc/free函数,当分配的空间大小超过128 bytes的时候使用第一级空间配置器;第一级配置器`__malloc_alloc_template`
第二级空间配置器使用了内存池技术,当分配的空间大小小于128 bytes的时候,将使用第二级空间配置器。第二级配置器`__default_alloc_template`
3、第一级配置器__malloc_alloc_template
以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作,并出现在类似于C++ new-handle机制。
C++ new-handle机制:要求系统在内存配置无法满足要求时,调用自己制定的函数。
4、第二级配置器__default_alloc_template
避免造成太多小额区块的内存碎片。
SGI第二级配置器主动将小额区块的内存需求上调至8的倍数。
SGI STL的第二级内存配置器维护了一个free-list数组用于管理:
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块。
free-list的节点结构如下:
union obj
{
union obj* free_list_link;
char client_data[1];
};
5、空间配置函数allocate()
此函数首先判断区块大小,大于128调用__malloc_alloc_template
,小于128检查对应的free-list。
/* n must be > 0 */
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;
//1、大于128字节就调用第一级配置器
if (n > (size_t) __MAX_BYTES)
{
return(malloc_alloc::allocate(n));
}
//寻找16个链表中适当的
my_free_list = free_list + FREELIST_INDEX(n);
//2、有可用区块
result = *my_free_list;
//3、链表没有可用的块调用refill申请
if (result == 0)
{
void *r = refill(ROUND_UP(n));
return r;
}
//调整链表不再指向这块被使用的块
*my_free_list = result -> free_list_link;
return (result);
};
6、空间释放函数:deallocte()
/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;
//大于128使用第一级配置器
if (n > (size_t) __MAX_BYTES)
{
malloc_alloc::deallocate(p, n);
return;
}
//找到所属链表
my_free_list = free_list + FREELIST_INDEX(n);
//调整链表回收区块
q -> free_list_link = *my_free_list;
//链表重新指向回收块
*my_free_list = q;
}
7、重新填充free-list:refill()
新的空间取自内存池(调用chunk_alloc())。缺省取20个新区块,但万一内存池不够,获得的新区快将少于20。
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
//默认申请块数
int nobjs = 20;
//nobjs为传引用
char * chunk = chunk_alloc(n, nobjs);
obj * __VOLATILE * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i;
//只有一块返回给调用者链表无新节点
if (1 == nobjs)
return(chunk);
//不止一块找到所属链表
my_free_list = free_list + FREELIST_INDEX(n);
//对新的区块建链表
result = (obj *)chunk;
//链表指向从内存池拿出的链表
*my_free_list = next_obj = (obj *)(chunk + n);
//将新链表之间的节点串接起来
for (i = 1; ; i++) //从开始第个返回给申请者
{
//当前节点
current_obj = next_obj;
//指向下一个节点
next_obj = (obj *)((char *)next_obj + n);
if (nobjs - 1 == i)
{
//尾节点置
current_obj -> free_list_link = 0;
break;
}
else
{
//当前节点指向下一个节点
current_obj -> free_list_link = next_obj;
}
}
return(result); //返回给申请者
}
8、内存池
chunk_alloc()工作原理:
1、 内存池有足够大小的空间,则分配申请的空间;
2、 内存池没有足够大小的空间,但是至少还能分配一个节点的空间,则能分多少分多少;
3、 内存池一个节点都腾不出来,向系统的heap**申请2倍于要求大小的空间**,在此之间,如果内存池剩余有空间,则放到free-list中去;
4、 配置heap空间,补充内存池。如果向heap申请空间失败,那么只能看free-list中更大的节点是否有可用空间了,有则用之,同时递归调用自身修正chunk_alloc(size,__nobjs);
5、 如果free-list也没有可用节点了,那么转向第一级空间配置器申请空间;
6、 再不行,第一级空间配置器就抛出bad_alloc异常。
2.3内存基本处理工具
1、uninitialized_copy
template<class InputIterator,class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first,InputIterator last,ForwardIterator result)
作为输出目的地的[result,result+(last-first))范围内的每个迭代器都指向未初始化区域,则uninitialized_copy()调用copy construct,将[first,last)范围内的每个对象的复制品放入输出范围。
有两个特化版本,分别为char*,wchar_t*
。
2、uninitialized_fill
template<class ForwardIterator,class T>
void uninitialized_fill(ForwardIterator first,ForwardIterator last,const T& x)
作为输出目的地的[first,last)范围内的每个迭代器都指向未初始化区域,则uninitialized_fill在该范围内产生x的复制品。
3、uninitialized_fill_n
template<class ForwardIterator,class size,class T>
ForwardIterator uninitialized_fill_n(ForwardIterator first,size n,const T& x)
作为输出目的地的[first,first+n)范围内的每个迭代器都指向未初始化区域,则uninitialized_fill_n
在该范围内产生x的复制品。
在实现代码中,若迭代器first的value_type为POD(Plain Old Data也即是标量型别)型别,则对POD采取高阶函数执行,对non-POD采取保险的安全做法。
参考:
http://blog.csdn.net/will130/article/details/51315647
http://www.cnblogs.com/guyan/archive/2012/09/10/2678606.html
以上是关于STL学习笔记--2空间配置器 allocator的主要内容,如果未能解决你的问题,请参考以下文章
STL源码剖析——空间配置器Allocator#3 自由链表与内存池