stl源码剖析

源码之前 了无秘密

STL概述

stl 不仅时可复用组件库 还是包罗算法与数据结果的软件框架 有六大组件 容器 算法 迭代器 仿函数 配接器

GPL协议 使用者可以自由阅读与修改GPL软件的源代码 但如果使用着要传播借助GPL软件而完成的软件 他们必须同意GPL规范

SGI STL 时可读性最高的一份STL

模板全特化 对于特定类型 需要对模板特化
eg:

template <class T>
class Compare
{
     public:
     bool IsEqual(const T& arg, const T& arg1);};

template <>
class Compare<float>
{
     public:
     bool IsEqual(const float& arg, const float& arg1);};

template <class T> 
bool Compare<T>::IsEqual(const T& arg, const T& arg1)
{
     cout<<"Call Compare<T>::IsEqual"<<endl;
     return (arg == arg1);}

bool Compare<float>::IsEqual(const float& arg, const float& arg1){
     cout<<"Call Compare<float>::IsEqual"<<endl;
     return (abs(arg - arg1) < 10e-3);}

模板偏特化 提供另一份template定义式 本身仍然时模板化的 只是针对template参数更一步的条件限制所设计出来的特化版本。

eg:

template <class _Iterator>
struct iterator_traits{
 typedef typename _Iterator::iterator_category iterator_category;
 typedef typename _Iterator::value_type        value_type;
 typedef typename _Iterator::difference_type   difference_type;
 typedef typename _Iterator::pointer           pointer;
 typedef typename _Iterator::reference         reference;};

// specialize for _Tp*
template <class _Tp>
struct iterator_traits<_Tp*> {
 typedef random_access_iterator_tag iterator_category;
 typedef _Tp                         value_type;
 typedef ptrdiff_t                   difference_type;
 typedef _Tp*                        pointer;
 typedef _Tp&                        reference;};

空间配置器

class Foo{…}
Foo *pf = new Foo;
deflete pf

new有两段操作 调用::operater new 配置内存 调用 Foo::Foo() 构造对象内容 delete也有两段操作 Foo::~Foo() 将对象析构 调用::operator delete释放内存
内存配置alloc::allocate()由负责 内存释放由alloc::deallocate()负责 对象构造由::construct()负责 对象析构由::destroy()负责
stl 规定配置器:

construct 和 destroy

construct 接受一个指针和一个初值value 通过placement new 将初值设置到指定空间上

destroy 一个时只接受一个指针 调用该对象的析构函数 另一个时接受first和last迭代器 将[first,last)范围内的对象析构 并通过 typetraist判断该类型的析构函数是否无关痛痒
若是什么都不做 如不是则析构该对象

alloc设计哲学:向heap请求空间 考虑多线程 应对内存不足 应对内存碎片问题

SGI设计了双层配置器 配置区超过128字节 采用第 一级配置器 malloc + free 第二级配置器根据不同情况采取不同策略

第一级配置器 使用malloc和free 并封装一层接口

第二级配置器维护一个free_list[16] 分别指向8的倍数且不大于128区块,当free list喂初始化或空间不够 从内存池申请空间。

POD意指 Plain Old Data 也就是标量型别或传统的C struct型别。POD型别必然拥有trivial ctor/dtor/copy/assignment函数 可以对POD型采用最有效率的填写法 对非POD采用保险的构造函数做法。

迭代器 与traits编程技法

迭代器 一种设计模式 提供一种方法 能够依次访问聚合物的各个元素 而又无需暴露该聚合物的内部表达方式。迭代器是一种智能指针。
迭代器相应的型别:利用function template的参数推到机制
ctor == constructor
dtor == destructor

traits扮演“特性萃取机” 萃取各个迭代器特性 。若要traits有效工作 每个迭代器需要遵循约定 自行以内嵌型别定义的方式定义出相应型别。对于原型指针 可以使用偏特化模板

最常用的迭代器型别 value type , difference type, pointer, reference, iterator category

value type 迭代器所指对象的型别

difference type 两个迭代器之间的距离

reference type 迭代器所指之物引用

pointer type 迭代器所指之物的指针

iterator category 迭代器的类别,不同类别,函数实现方式不同,类别如下所示:

traits编程技法 利用 “内嵌型别”的编程技巧和编译器template参数推导功能,增强c++未能提供的关于型别认证方面的能力。

序列式容器

vector 动态空间 实现的关键是大小的控制和重新配置时数据移动的效率

vector insert的三种情况

插入点后元素个数大于插入元素个数

插入点后元素个数小于插入元素个数

空间不够

list 双向链表 环状双向链表 使用一个空白节点标记环链表的尾端

transfer操作源代码 修改六个结点的指针 6次指针修改

list的transfer操作

list的sort算法

list<T, Alloc> counter[64]存放归并过程的中间变量
counter[0]里存放小于2^1次方个元素 个数为2时进位并置空
counter[1]里存放小于2^2次方个元素 个数为4时进位并置空
counter[2]里存放小于2^3次方个元素 个数为8时进位并置空

使用merge进行归并排序

参考链接:

https://blog.csdn.net/qq276592716/article/details/7932483

deque
双向开口的连续线性空间,通过指针的指针map来管缓冲区

deque 迭代器示意图

deque的构造 设置map_pointer的大小 为每个node分配空间 设置iterator start finish

stack 后进先出 container adapter 没有迭代器 底层可以是list或deque

queue 先进先出 container adapter 没有迭代器 底层可以是list或deque

heap 没有迭代器
完全二叉树 最大堆或最小堆 stl提供的是最大堆

push_heap 算法 将插入值放入尾端 根据值大小向上调整

pop_heap算法 根据值向下调整 将头结点置于尾端

sort_heap算法 不断使用pop_heap使之有序 排序后的heap不是合法的

priority_queue 优先队列 缺省情况下利用max-heap实现 底端插入 顶层输入权值最大的元素 container adapter 没有迭代器

slist 单向链表

关联式容器

avl树 左右子树的高度最多相差1 单旋转 双旋转
红黑树 平衡二叉搜索树 双向迭代器
(1) 每个节点不是红就是黑
(2) 根是黑
(3) 节点为红 其子节点必须为黑
(4) 任一节点至NULL的任何路径 其黑色节点数相同

插入操作详见 算法导论

set 底层为RB-tree RB-tree是set中的成员变量 insert erase操作基于RB-tree的insert erase,multiset 允许键值重复 调用的是RB-tree的insert_equal() 而不是insert-unique()

map 所有元素是pair两个元素不能拥有相同的键值 底层为RB-tree RB-tree中元素是pair<key, value> RB-tree是set中的成员变量 insert erase操作基于RB-tree的insert erase,multimap 允许键值重复 调用的是RB-tree的insert_equal() 而不是insert-unique()

hashtable
插入 删除 查询 常数时间

散列函数 计算出某个函数的插入位置 若位置已经被用 则顺序向下寻找到空位置为止
线性探测 两个条件 表格足够大 每个元素相互独立

二次探测 计算出的位置是H 依序尝试的H+1^2 H+2^2 H+3^2 … H+i^2 假设表格大小为质数 且永远保持负载系数为0.5以下 每插入一个元素 探测次数不多于2

开链 hash函数为我们分配某一个list 在这个list上进行插入 删除 查询操作 如果list足够短 速度还是很快的。
维护linked list的桶子不是STL中的list或slist 而是vector

hashtable iterator 中包含hashtable的成员变量 标记是forward_iterator_tag 只重载了operator++ 没有重载operator–

hashtable vector<node*, Alloc> buckets作为成员变量 管理
所有节点 size_type num_elements 存储元素总数
参数:
Value 节点实值型别
Key 节点键值型别
HashFunc 哈希函数
ExtractKey 从节点中取出键值的方法(函数或仿函数)
EqualKey 判断键值相同与否
Alloc 空间配置器

SGI STL以质数设计表格大小 先将28个质数计算好 提供最接近某数并大于某数的质数

表格重建判断条件 插入后元素总数大于 bucket vector的大小

resize 更加元素总数重新筛选质数 并分配大小 并将原来vector中的list 链接到新的vector中

hash_set hash_map hashtable作为成员变量 所有操作调用hashtable对应版本 不支持String 支持char*, hash_map中节点存储的值时pair<key, value>

hash_multiset hash_multimap 调用的是insert_equal() 而不是insert-unique()

算法

质变算法 会改变对象的值 如sort
非质变算法 不会改变对象的值 如for_each

所有算法的前两个参数都是一对迭代器 first和last 习惯采用前闭后开 [fist,last)

仿函数

函数对象 又称仿函数 一组具有函数特质的对象
SGI STL中分为一元与二元运算 或 算术类 关系类与逻辑类


实例:

配接器

一种设计模式 可以应用于仿函数 容器 迭代器

迭代器配接器 使得迭代器更很好用 类别 output_iterator_tag

resverse例子

仿函数迭代器 通过函数之间的绑定 组合 修饰能力 几乎可以无限创造出各种可能的表达式 成员函数类型时 X*或X& 可以使用关键virtual 借此实现多态

仿函数实例

仿函数count_if剖析

容器迭代器 stack queue 装饰模式 真正起作用的类实例作为配接器的成员变量