STL in C++11 (Allocator 3)

好了,经过之前的铺垫,我们终于要正式开始完成Allocator的代码了。在之前,我们先来看一下到底什么是Allocator。

Allocator(分配器)是一个用来管理容器内存的类,由它来负责一个容器内部的内存的分配与释放。

举个例子:
std::vector的完整声明为:

template<typename T, typename Alloc = allocator<T>>
class vector; 

可以看到, std::vector有两个模板参数,T代表元素类型, Alloc代表分配器类型。
我们平时使用vector之所以不需要指明Alloc参数是因为它已有默认值。默认的分配器std::allocator<T>负责容器的内存分配与释放。

一个Allocator在C++中必须满足下列条件:

  1. 内部有一个value_type的类型定义,用来表示元素类型
  2. 一个构造函数
  3. 一个template构造函数, 用来实现当类型改变时,内部状态的复制
  4. 一个成员函数allocate(), 分配内存
  5. 一个成员函数deallocate(), 释放内存
  6. 提供分配器的操作符 == 与 !=

可能这些条件看起来有些抽象, 那么我们赶快来实践一下吧。

我们这个STL项目实现的分配器为free_list_allocator,而free_list_allocator由另一个分配器malloc_allocator组成。我们先来实现malloc_allocator,再实现free_list_allocator。顾名思义,malloc_allocator是借助malloc实现的allocator。

template<typename T>
class malloc_allocator
{
    
public:
    typedef T value_type;

private:
    //oom: out of memory
    static T *oom_malloc(size_t n);
    static T *oom_realloc(T *p, size_t n);
    
    static void (*oom_handler)();


public:
    malloc_allocator() {}
    template<typename U>
    malloc_allocator(const malloc_allocator<U>&) {
        //no state to copy
    }

    static T *allocate(std::size_t num);
    static T *reallocate(T *p, std::size_t num);
    static void deallocate(T *p, std::size_t num);


    void (*set_oom_handler(void (*handler)()))();
};

以上是malloc_allocator声明的代码, 我们一步一步分析。
根据上面提到的Allocator的要求, 我们定义类型value_type为T,代表值类型。同时实现分配器的构造函数与模板构造函数,这两个构造函数没有任何代码,因为malloc_allocator没有需要维护的状态。

那么静态变量oom_handler是用来干什么的?

static void (*oom_handler)();

oom 是 out of memory 的缩写,故oom_handler即为一个指向内存不足处理函数的指针。这个内存不足处理函数由分配器的使用者提供,借助分配器的成员函数set_oom_handler()设置。 set_oom_handler声明如下:

void (*set_oom_handler(void (*handler)()))();

set_oom_handler的声明的确有点复杂,实际上set_oom_handler是一个函数,它接受一个函数指针,并且返回一个函数指针。
set_oom_handler实现:

template<typename T>
void (*malloc_allocator<T>::set_oom_handler(void (*handler)()))()
{
    auto old_handler = oom_handler;
    oom_handler = handler;
    return old_handler;
}

set_oom_handler内部逻辑很简单,即接受新的函数指针同时返回旧指针。通过调用它来设置不同的函数来处理内存不足。

接下来我们再来看oom_malloc函数,它用来在内存不足(out of memory)的情况下通过使用oom_handler申请内存。实现如下:

template<typename T>
T *malloc_allocator<T>::oom_malloc(std::size_t n) 
{
    T *result = static_cast<T*>(malloc(n));

    while (result == nullptr)
    {
        if (oom_handler == nullptr)
            throw std::bad_alloc();
        oom_handler();

        result = static_cast<T*>(malloc(n));
    }

    return result;
}

oom_malloc首先尝试使用malloc申请 n bytes 的空间,并且将指针转化为T *类型。当结果为 nullptr , 即内存不足时,调用处理函数来获取内存(如果有的话),否则抛出 bac_alloc 异常。
同理, oom_realloc的实现:

template<typename T>
T *malloc_allocator<T>::oom_realloc(T *p, std::size_t n)
{
    T *result = static_cast<T*>(realloc(p, n));
    while (result == nullptr)
    {
        if (oom_handler == nullptr)
            throw std::bad_alloc();
        oom_handler();
        
        result = static_cast<T*>(realloc(p, n));
    }

    return result;
}

至此, 我们已经实现了oom_malloc与oom_realloc, 我们现在通过它们实现Allocator最重要的几个函数: allocate(), deallocate()。我们一个一个来。

  1. allocate(std::size_t num)
template<typename T>
T *malloc_allocator<T>::allocate(std::size_t num) 
{
    T *result = static_cast<T*>(malloc(num * sizeof(T)));
    if (result == nullptr) {
        result = oom_malloc(num * sizeof(T));
    }

    return result;
}

allocate(num)分配num个元素的空间, 如果内存不足就调用oom_malloc。

  1. reallocate(T *p, std::size_t num)
template<typename T>
T *malloc_allocator<T>::reallocate(T *p, std::size_t num) 
{
    T *result = static_cast<T*>(realloc(p, num * sizeof(T)));
    if (result == nullptr) {
        result = oom_realloc(p, num * sizeof(T));
    }

    return result;
}

我们调用reallocate在指针p所指的区域重新分配num个元素的空间, 如果空间不足则调用oom_realloc。

  1. deallocate(T *p, std::size_t num)
template<typename T>
void malloc_allocator<T>::deallocate(T *p, std::size_t num) 
{
    free(p);
}

deallocate负责回收指针p所指的num个元素的内存空间, 其中num参数在此无需使用,我们直接free(p)就好了。

最后一步,我们还需提供 == 与 !=

template<typename T1, typename T2>
bool operator==(const malloc_allocator<T1>&, const malloc_allocator<T2>&)
{
    return true;
}

template<typename T1, typename T2>
bool operator!=(const malloc_allocator<T1>&, const malloc_allocator<T2>&)
{
    return false;
}

我们将 == 的比较结果一直设为true, 因为我们只关心两个分配器是否采用同一种内存分配策略,而不在乎具体的元素类型。

至此,我们已经实现了自己的第一个分配器了。我们现在可以调用malloc_allocator的allocate()与deallocate()来分配释放内存。

下一篇文章就开始进入free_list_allocator了, 这个分配器的确十分复杂,预计要花很久的时间才能写完。而malloc_allocator 是 free_list_allocator的基础,理解malloc_allocator十分有利于我们接下来的学习。

下一篇 STL in C++11 (Allocator 4)

P.S 谢绝转载,谢谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • 我们现在终于要结束分配器部分的内容了,这是 Allocator 的最后一篇文章了。 上次我们还留下了两个函数没有实...
    Puppas阅读 286评论 0 1
  • 我们在之前的文章学习了编写malloc_allocator,它是一个借助malloc分配内存的分配器,并且实现了在...
    Puppas阅读 601评论 0 2
  • 主要内容: 本节主要讲解了面向对象和泛型编程的区别,以及source code所涉及到的基础知识(包括运算符重载、...
    竹林柳岸阅读 404评论 0 1
  • 源代码分布 标准库STL的文件位置,与所采用的编译器有关: visual c++源代码文件:..\include ...
    readME_boy阅读 705评论 0 0
  • 配置器 空间适配器的标准接口 内存分配释放与构造析构 为了精密分工,STL allocator决定将这两阶段操作区...
    VictorHong阅读 541评论 0 0