STL源码剖析第二章---配置器

Posted chyauang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL源码剖析第二章---配置器相关的知识,希望对你有一定的参考价值。

一、概述

SGI-STL有两级空间配置器:标准空间配置器std::allocator和特殊空间配置器std::alloc。


二、标准空间配置器std::allocator

之所以称之为"标准"是因为它直接使用了::operator new和::operator delete来进行内存分配,在效率上表现的并不好。


三、特殊的空间配置器std::alloc

  new和delete实际上是由两个操作组成的:new首先分配内存,然后调用ctor构造对象;delete首先调用dtor析构对象,然后释放内存。

  为了提高效率,SGI STL的默认空间配置器将这两个操作分离开来:内存配置交由alloc::allocate(),对象构造交由alloc::construct();对象析构交由alloc::destroy(),内存释放交由alloc::deallocate()来完成。

  配置器定义于<memory>中,内含的文件及其内容见下图:

技术分享图片

接下来我们将分别对这三个文件进行研究。


3.1 <stl_construct.h>:构造与析构的基本工具

  destory()与construct()的泛化与特化版本关系见下图:

技术分享图片

  特别需要注意的是trivial dtor判断那一部分,这一部分是提高析构效率的关键所在。

  我们知道,构造和析构的成本是很高的,所以为了提高效率,我们应做的是避免不必要的构造与析构操作。对于构造操作,由于C++标准内有内置的placement new机制(将内存配置和对象构造分离开来),所以可以直接使用将降低成本交给编译器来做。但析构操作是没有对应placement delete机制的([见这里](https://www.zhihu.com/question/22947192)),所以这个工作就需要我们来做。

  如何避免不必要的析构来尽量降低析构的成本呢?这里就引入了trivial这个概念。简单说来,若一个类中含有指针成员并对其进行了内存配置,那么这个类就是non trivial的,对于这种类型的对象,是不可避免地要使用其dtor进行析构的;若需要析构的对象是POD类型数据(标量型别或传统的C struct型别),那么可以不使用析构函数而是是直接使用memcpy()等成本更低的函数替代。

  如何通过程序来判断一个类的trivial属性呢?SGI STL是使用value_type()配合__type_traits()来进行判断的(见第三章)。为什么要对(char*, char*)和(wchar_t*, wchar_t*)进行特化也是这个原因,可以使用成本更低的memmove()来进行析构。

  destory()的最后一个特化版本就是就直接的析构了,在non trivial条件下调用。


3.2 <stl_alloc.h>:空间的配置与释放

  SGI STL的默认空间配置器由两级组成,一级处理较大块空间(大于128bytes)的配置与释放,并处理内存不足状况,另一级配合内存池处理较小块空间的配置与释放,避免内存碎片的产生。
  两级空间配置器关系如下:

技术分享图片

  第一级配置器直接使用malloc()来实现allocate()进行空间配置,直接使用free()来实现deallocate()进行空间释放,模拟C++的set_new_hander()来处理内存不足情况(当第二级配置器遭遇内存不足时转向这里进行处理)。

  所谓set_new_hander(),其实就是在内存不足时另指定一个错误处理函数。一般来说,在内存不足发生时,这个错误处理函数会会不断地调用不断地试图申请内存直至申请成功。若错误处理函数未被指定,则会返回bad_alloc异常信息,或直接终止程序。

  第二级配置器需要维护一个链表数组,数组中每一个链表的每一项是一个union结构,或为表首的内存块,或为指向下一项的指针。数组中的16个链表管理着8、16、24、32、40、48、56、64、72、80、88、96、104、112、120和128bytes的内存块。

  第二级配置器对内存的分配和释放具体逻辑如下:
    (1) 首先要先在数组中根据内存块大小进行位置定位,确定对应的是哪一个链表项后,若链表不为空且其中的内存块能满足我们的需求的话,剩下的就是简单的链表增/删元素的操作了;
    (2) 若不能满足我们的需求的话,将向内存池申请2倍于我们需求的内存块对相应链表进行填充(refill()--->chunk_alloc()); 
    (3) 若内存池也所剩不多不能满足需求时,则其剩余的容量将补充至合适的链表内然后向heap空间申请内存;
    (4) 若heap空间也不足了,那么配置器将遍历其后对应着更大内存块的链表,寻求可用的内存补充至内存池,再转至转至(1);
    (5) 若这样子也找不到可用内存,到了几乎山穷水尽的地步,第二级配置器将会转向第一级配置器,希望其set_new_hander()能否带来一丝转机。


3.3 <stl_uninitialized.h>:内存基本处理工具

//若操作对象是POD对象则直接调用对应高阶函数执行更高效操作,否则调用相应construct()进行构造

//对应高阶函数copy(),使用ctor对范围内物体进行构造,针对(char*, char*)与(wchar_t*, wchar_t*)进行特化,原因同上。construct(&*(result + (i - first)), *i)。
template <class InputIterator, class ForwardIterator>
ForwardIterator
unitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);


//对应于高阶函数fill(),其他同上。construct(&*i, x)。
template <class ForwardIterator, class T>
void unitialized_fill(ForwardIterator first, ForwardIterator last, const T &x);

//对应于高阶函数fill_n(),其他同上。construct(&*i, x)。
template <class ForwardIterator, class Size, class T>
ForwardIterator
unitialized_fill_n(ForwardIterator first, Size n, const T &x);

注脚

这些读书笔记均为平时读书时随手记录下来的,之前一直分散在各处,特此将它们集中在一起,便于今后复习用。








以上是关于STL源码剖析第二章---配置器的主要内容,如果未能解决你的问题,请参考以下文章

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

STL源码剖析 学习笔记

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

《STL源码剖析》要点摘抄

STL源码剖析之allocator

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