一、初识STL空间配置器
空间配置器作为隐藏在一切背后的关键组件,是STL源码的重要内容。事实上allocator不应该称为空间配置器,而应该称为内存配置器。因为空间并不一定是内存,也可以是磁盘或者其他辅助存储介质(也可以编写一个allocator直接向磁盘取空间)。
空间配置器设计的必然性:
容器存放了STL的操作对象(可以是数值或其他数据),对空间的分配是必不可少的环节。而针对泛型编程的历史沿革,将一系列容器与其分配空间的方法集成是面向对象编程历史发展的必然。同时,针对空间分配带来的各类问题配置器的功能也在细分:
一级配置器:直接使用malloc()、free()、relloc()来配置内存;
二级配置器则针对以下问题:
1、小块内存带来的内存碎片问题
单从分配的角度来看。由于频繁分配、释放小块内存容易在堆中造成外碎片(极端情况下就是堆中空闲的内存总量满足一个请求,但是这些空闲的块都不连续,导致任何一个单独的空闲的块都无法满足这个请求)。
2、小块内存频繁申请释放带来的性能问题。
关于性能这个问题要是再深究起来还是比较复杂的,下面我来简单的说明一下。
开辟空间的时候,分配器会去找一块空闲块给用户,找空闲块也是需要时间的,尤其是在外碎片比较多的情况下。如果分配器其找不到,就要考虑处理假碎片现象(释放的小块空间没有合并),这时候就要将这些已经释放的的空闲块进行合并,这也是需要时间的。
因此维护了一个(free-list)来分配(memory pool)内存池的空间。
二、空间配置器的标准接口
作为空间配置器,应该具备分配空间和释放空间的能力,因此allocator有四个重要的功能:申请内存(allocate)、释放内存(deallocate)、构造对象(construct)、析构对象(destroy)。
具备以上能力后还应该具备针对各类型数据的接口:
通过迭代器中的萃取将会甄别出不同类型数据进行不同的处理。
allocator::value_type;//值
allocator::pointer;//指针
allocator::const_pointer;//常指针
allocator::reference;//引用
allocator::const_reference;//常引用
allocator::size_type;//unsigned类型,表示容器中元素长度或者下标
allocator::difference_type;//signed类型,表示迭代器差距
三、编写一个简单的空间配置器
以下是书中源码:
//.h文件
#ifndef _JJALLOC_
#define _JJALLOC_
#include <new>
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>
namespace JJ
{
//四个功能实现
//申请空间
template<class T>
inline T* _allocate(ptrdiff_t size, T*){
set_new_handler(0);//异常处理函数,传入0视为放弃异常处理
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if(tmp == 0){
cerr << "out of memery" << endl;
exit(1);
}
return tmp;
}
//释放空间
template<class T>
inline void _deallocate(T* buffer){
::operator delete(buffer);
}
//构造对象
template <class T1, class T2>
inline void _construct(T1 * p, const T2& value){
new(p) T1(value);
}
//析构对象
template <class T>
inline void _destroy(T* ptr){
ptr->~T();
}
template <class T>
class allocator{
public:
//针对标准STL的接口
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;
//一个嵌套在内的class template,class rebind<U>拥有唯一成员other
//作为接口存在,以便兼容传入的配置器
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
//.cpp文件
//测试内容
#include "jjalloc.h"
#include <vector>
#include <iostream>
using namespace std;
int main()
{
int ia[5] = {0,1,2,3,4};
unsigned int i;
vector<int , JJ::allocator<int> > iv(ia, ia+5);
for(i=0;i<iv.size(); i++)
cout << iv[i] << ' ';
cout << endl;
system("pause");
}
针对以上代码存在以下两点分析:
1._allocator中的T*参数的意义:
作为提示而从上层传入,当实现时会用来增进区域性。作为占位参数传入,可作为单纯的区域兼容提示。
2.rebind的兼容性问题:
1)Allocator是T的分配器,但其内部实现策略容器并不知道;
2)类型T和类型U在逻辑上是相关的,比如在链表中,数据类型T和结点类型Node是有联系的;
3)容器希望按照和T一样的策略(具体的说就是相同的allocator模板名)来分配U类型的对象。
四、一二级配置器
参考博客:https://blog.csdn.net/u012481976/article/details/82811623