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

时间: 2024-08-03 08:57:53

STL学习笔记--2、空间配置器 allocator的相关文章

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

前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配置器,配置的对象,是内存.(以下内容来自<STL源码剖析>) 空间配置器的标准接口 根据STL的规范,allocator的必要接口 各种typedef 1 allocator::value_type 2 allocator::pointer 3 allocator::const_pointer 4

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator delete().这两个全局函数相当于C的malloc() 和free() 函数.而SGI正是以malloc() 和free() 完成内存的配置与释放. 考虑到小型区块可能造成的内存破碎问题,SGI设计了两级的空间配置器.第一级直接使用malloc() 和free() ,而第二级则视情况采用不同的策略:当

STL源码剖析——空间配置器Allocator#3 自由链表与内存池

上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请.但只是对其概念进行点到为止的认识,并未深入探究.这节就来学习一下自由链表的填充和内存池的内存分配机制. refill()函数——重新填充自由链表 前情提要,从上节第二级配置器的源码中可以看到,在空间配置函数allocate()中,当所需的某号自由链表为空时,才会调用refill()函数来填充链表.refill()函数默认申请20块区块的内存(5行),但所得内存不一定就是20块,要看当前内存池的剩余情况和堆容量的

STL源码分析--空间配置器的底层实现 (二)

STL源码分析-空间配置器 空间配置器中门道 在STL中的容器里都是使用统一的空间配置器,空间配置器就是管理分配内存和销毁内存的.在STL将在heap空间创建一个对象分为两个步骤,第一是申请一块内存,第二是在这块内存中初始化一个对象.首先申请空间是由malloc提供,初始化一个对象时由constructor管理.销毁一个对象也是由两步骤完成,第一是销毁空间上的对象,第二是释放这块内存. 同时,STL的空间配置器分为两级内存,如果申请的内存空间大于128KB,那么就使用第一级空间配置,如果小于,那

C++ STL学习之 空间配置器(allocator)

标签(空格分隔): C++ STL 众所周知,一般情况下,一个程序包括数据结构和相应的算法,而数据结构作为存储数据的组织形式,与内存空间有着密切的联系. 在C++ STL中,空间配置器便是用来实现内存空间(一般是内存,也可以是硬盘等空间)分配的工具,他与容器联系紧密,每一种容器的空间分配都是通过空间分配器alloctor实现的.理解alloctor的实现原理,对内存结构以及数据存储形式会有更清晰直观的认识. 1.两种C++类对象实例化方式的异同 在c++中,创建类对象一般分为两种方式:一种是直接

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

STL源码剖析--空间配置器

STL的设计非常巧妙,组件间互取短长,形成了一个世界,这是这个世界里的组件: 1. containers(容器):所谓容器,是指存放数据的地方,将数据以一定的方法组织存放.根据不同的组织方式,可以把容器分为顺序容器,如vector.deque.list,关联容器,如set.map.Container是一种class template. 2. algorithm(算法):各种常用不常用的算法如sort.copy.search等等.algorithm是一种function template. 3.

STL源码分析--空间配置器 第一级配置器

一.SGI STL配置器简介 SGI STL的配置器与众不同,它与标准规范不同.如果要在程序中明确使用SGI配置器,那么应该这样写: [cpp] view plaincopyprint? vector<int,std::alloc> iv; 他的名字是alloc,而且不接受任何参数.标准配置器的名字是allocator,而且可以接受参数. SGI STL的每一个容器都已经指定了缺省配置器:alloc.我们很少需要自己去指定空间配置器.比如vector容器的声明: [cpp] view plai

[C++] 空间配置器——allocator类

1.new和delete有一些灵活性上的局限:new把内存分配和对象构造组合在了一起:delete将对象析构和内存释放组合在了一起. 2.当分配一大块内存时,我们通常计划在这块内存上按需构造对象,在此情况下,我们希望将内存分配和对象构造分离:这意味着我们可以分配大块内存,但只在真正需要的时候才真正执行对象创建操作. 3.allocator类,定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来,它提供一种类型感知的内存分配方法,它分配的内存是原始的.未构造的.在分配内存时,它会根据