Bootstrap

STL源码分析(总结)

STL六大组件

  1. 容器(containers):是一种class template,里面包含各种数据结构。
  2. 算法(algorithms):是一种function template,里面包含各种算法。
  3. 迭代器(iterators):是所谓的泛型指针,每个容器都有自己的专属的迭代器,知道如何遍历自己的元素。
  4. 仿函数(functors):是一种重载了operator()的class或class template,可作为算法的某种策略。
  5. 配接器(adapters):是一种用来修饰容器或仿函数或迭代器接口的东西。
  6. 配置器(allocators):是一个实现了动态空间配置,空间管理,空间释放的class template,负责空间配置与管理。

空间配置器

构造

  1. 分配新的空间
  2. 在新空间上构造对象
template <class T>
inline T* _allocate(...) {
    ...
    T* tmp = (T*)(::operate new((size_t)(size * sizeof(T))));
    ...
}
template <class T1, class T2>
inline void _construct(T1* p, const T2& value) {
    new(p) T1(value);   //placement new.
}

operator new
(1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
->如果有new_handler,则调用new_handler,否则
->如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则
->返回0
(2)可以被重载
(3)重载时,返回类型必须声明为void*
(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
(5)重载时,可以带其它参数

Placement new

placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本代替(不像普通版本的operator new和operator delete能够被替换)。

对象的分配

在已分配的缓存区调用placement new来构造一个对象。
Task *ptask = new (buf) Task

SGI中的空间配置与释放(std::alloc)

SGI设计哲学:
- 向system heap要求空间
- 考虑多线程状态
- 考虑内存不足时的应变措施
- 考虑过多“小型区块”可能造成的碎片问题

内存分配中使用::operator new()与::operator delete()分配与释放相当于molloc()与free().

考虑第四点设计了双层配置器,第一层直接调用malloc()和free().
配置内存小于128bytes时,使用第二级配置器:
使用16个自由链表负责16种小型区块的次级配置能力。如果内存不足转一级配置器。

8 16 24 32 40 48 56 64 72 80 88 96 104 112 120 128

迭代器

迭代器类似于一种智能指针,比较重要的行为有内容提领成员访问

迭代器相应型别

为了让迭代器适应不同种类的容器即泛化,需利用function template的参数推导机制推导型别。

偏特化如果class template拥有一个以上的template参数,我们可以针对其中某个template参数进行特化工作(我们可以在泛化设计中提供一个特化版本)。

trans编程技法

声明内嵌型别推导函数返回值:

template <class T>
struct MyIter {
    typedef T value_type;   //内嵌型别
    T* ptr;
    MyIter(T* p=0) : ptr(p) {}
    T& operator*() const { return *ptr; }
    //...
}

template <class I>
typename I::value_type  //这一整行是func的返回值型别
func(I ite) {
    return *ite;
}

MyIter<int> ite(new int(8));
cout << func(ite);

萃取型别:

template <class I>
struct iterator_traits {
    typedef typename I::value_type value_type;
}

template <class I>
typename iterator_traits<I>::value_type //函数返回型别
func(I ite) {
    return *ite;
}

//萃取原生指针,偏特化版本
template <class T>
struct iterator_traits<T*> {
    typedef T value_type;
};

STL根据经验,定义了迭代器最常用到的五种类型:value_type、difference_type、pointer、reference、iterator_category,任何开发者如果想将自己开发的容器与STL结合在一起,就一定要为自己开发的容器的迭代器定义这五种类型,这样都可以通过统一接口iterator_traits萃取出相应的类型,下面列出STL中iterator_traits的完整定义:

tempalte<typename I>
struct iterator_traits
{
    typedef typename I::iterator_category iterator_category;
    typedef typename I::value_type value_type;
    typedef typeanme I:difference_type difference_type;
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};

重点提一下iterator_category:
根据移动特性与施行操作,迭代器被分为五类:

  1. Input Iterator:只读
  2. Output Iterator:只写
  3. Forward Iterator:允许“写入型”算法(例如replace())在此种迭代器所形成的区间上进行读写操作。
  4. Bidirectional Iterator:可双向移动。
  5. Random Access Iterator:前四种迭代器都只供应一部分指针算术能力(前三种支持 operator++,第四种再加上operator–),第五种则涵盖所有指针算术能力,包括p+n, p-n, p[n], p1-p2, p1

序列式容器

序列式容器.jpg-7.5kB
元素可序但未必有序

vector

vector底层为动态数组,分配策略为:

  • 如果vector原大小为0,则配置1,也即一个元素的大小。
  • 如果原大小不为0,则配置原大小的两倍。

当然,vector的每种实现都可以自由地选择自己的内存分配策略,分配多少内存取决于其实现方式,不同的库采用不同的分配策略。
当以两倍空间增长时之前分配的内存空间不可能被使用,这样对于缓存并不友好。
相关连接:https://www.zhihu.com/question/36538542

vector的迭代器
使用vector迭代器时要时刻注意vector是否发生了扩容,一旦扩容引起了空间重新配置,指向原vector的所有迭代器都将失效

vector维护的是一个连续线性空间,与数组array一样,所以无论其元素型别为何,普通指针都可以作为vector的迭代器而满足所有必要的条件。vector所需要的迭代器操作,包括operator*,operator->,operator++,operator–,operator+=,operator-=等,普通指针都具有。

vector提供了Random Access Iterators。

vector的数据结构
vector底层为连续线性空间
此处输入图片的描述

list

list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表

对于迭代器,只能通过“++”或“–”操作将迭代器移动到后继/前驱节点元素处,而不能对迭代器进行+n或-n的操作。增加任何元素都不会使迭代器失效。删除元素时,除了指向当前被删除元素的迭代器外,其它迭代器都不会失效。

;