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 自由链表与内存池

STL源码剖析 — 空间配置器(allocator)

STL初探——第二级配置器 __default_alloc_template的学习心得

STL 之 空间配置器(allocator)

STL 之 空间配置器(allocator)

STL之空间配置器allocator