STL源码阅读(一)

本文介绍了SGI Standard Template Library (STL) 的核心组件,包括容器、算法、迭代器、仿函数、适配器和分配器。详细探讨了SGI分配器的设计目标和两级分配策略,特别是对于小内存区域的内存碎片问题的解决方案。此外,还讲解了迭代器的不同类型,以及C++11中POD类型的分类和特点。文章还概述了STL中各种容器如vector、list、deque等的实现技术和差异,并提到了关联容器如set、map的底层数据结构。最后,简要讨论了STL中的排序算法、查找算法和其他实用函数。
摘要由CSDN通过智能技术生成

STL源码阅读(一) (SGI STL v3.3)

STL提供六大组件:容器(containers),算法(algorithms),迭代器(iterators),仿函数(functors),
适配器(adapters),分配器(allocators)。

SGI分配器设计目标:
- 向system heap申请空间
- 考虑多线程情况
- 考虑内存不足时应变措施
- 考虑过多的小型内存区域可能造成的内存碎片情况

对于小型区域可能造成的内存碎片问题,SGI设计了两级分配器。

template <int __inst>
class __malloc_alloc_template

当内存大小大于128字节时,视为内存足够大,
使用一级分配器,使用malloc,free分配内存与释放内存,模拟C++ set_new_handler以处理内存不足的情况(
内存分配不足时,调用指定的处理函数,该函数尝试释放内存,然后再分配内存,一直到内存分配成功为止);

template <bool threads, int inst>
class __default_alloc_template

当内存小于128字节时,使用二级分配器不同的策略管理内存。维护16个自由链表,负责16个小内存区域的分配能力。
内存池以malloc分配而得,当内存不足,转调用一级分配器。如果需求内存区域大于128字节,转调用一级分配器。

迭代器类型:Input Iterator(只读),Output Iterator(只写),Forward Iterator(读写), Bidirectional Iterator,
Random Access Iterator。前3中只支持operator++,第4中支持operator++和operator–,第5中支持p+n、p-n、
p[n]、p1 - p2和p1 < p2。

C++11将POD(Plain Old data)划分为两个基本概念,只有满足这两个基本类型才能成为POD类型:
- 平凡的(trivial)。一个trivial class 或struct应满足一下条件,拥有平凡的默认构造函数和析构函数(编译器自动生成的);
拥有平凡的默认复制构造函数和平凡的移动构造函数;拥有平凡的复制赋值运算符和移动赋值运算符;不能包含虚函数和虚基类;
- 标准布局的(standard layout)。所有非静态成员都有相同的访问权限;在类继承时,满足:派生类中有非静态成员,且仅有
一个仅包含静态成员的基类。基类有非静态成员,而派生类没有非静态成员;类中第一个非静态成员和其基类不同;没有虚函数和
虚基类;所有非静态数据成员均符合标准布局类型,其基类也符合标准布局。

POD的优点:
1.字节赋值, 代码中我们可以安全的使用memset和memcpy函数对POD类型进行初始化和拷贝等操作.

2.提供了对C内存布局的兼容. C++程序可以与C函数 进行相互的操作, 因为POD类型的数据在C++与C之间的操作总是安全的.

3.保证了静态初始化的安全有效, 静态初始化在很多时候能够提高程序的性能, 而POD类型的对象初始化往往更加简单(比如放在目标文件的.bss段 , 在初始化中直接被赋0).

SGI STL的各种容器
Sequence Containers:array (built-in), vector, heap (in the form of algorithm),priority_queue, list, slist (non-standard), deque, stack (adapter), queue (adapter)。
Associate Containers: RB-tree (inner use),set, map, multiset, multimap,
hashtable (non-standard), hash_set (non-standard), hash_map (non-standard), hash_multiset (non-standard), hash_multimap (non-standard)。

vector的实现技术,关键在于其对大小的控制以及重新移动时的数据移动的效率。
vector维护的是一个连续的内存空间。注意:如果增加元素时,超过当时的容量,则容量会自动扩充至两倍。如果容量还不充足,知道扩充至满足为止。另外容量的
扩充经历的过程是:重新分配空间,移动元素,释放原空间等动作。

SGI STL list是一个环状双向链表,所以只需要一个指针,便可以完整表现整个链表。

vector单向开口的连续线性空间,deque双向开口的连续线性空间。deque和vector的最大差异在于,一是允许以常数时间在首尾端插入或移除操作,二在于deque没有所谓容量的概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。

deque采用一小块连续空间(称为map)作为主控,其中的每一个元素是一个指针,指向另一段连续线性空间(缓存区),SGI STL deque可以指定缓冲区的大小,默认值0表示512字节。当map已经满载时,会重新分配较大的一块区域,拷贝原内容到新map。

stack容器适配器,默认使用的容器是deque,也可以是其它满足特定要求(见参考手册)的容器。

queue容器适配器,默认使用的容器是deque,同样可使用其它满足特定要求(见参考手册)的容器。

priority_queue同样是一个容器适配器,默认使用容器是vector,一般内部是以最大二叉堆实现的(binary max heap)。binary heap是一种完全二叉树(complete binary tree)。

slist,单向列表。C++11中添加了一个forward_list,支持在容器的任何位置快速的插入和删除元素,但不支持快速随机访问,另外相对于list来说,更加节省内存空间。

关联式容器set、map、multiset与multimap底层一般都是使用RB-tree(一种平衡二叉树)实现的。hash_set, hash_map, hash_multiset, hash_multimap一般是使用hash table实现的。现在C++11提供了哈希表实现的容器unordered_set, unordered_multiset, unordered_map, unordered_multimap。

哈希表
负载系数:元素个数除以表格个数。
解决碰撞问题的方法有:linear probing(解决碰撞问题的函数是一个线性函数),quadratic probing(解决碰撞问题的函数是一个二次函数),separate chaining(每个表格元素维护一个链表)等。

SGI STL的hash table采用的是separate chaining method。

注意:RB-tree有自动排序功能,而hash-table没有,这也体现到分别以她们为底层实现的容器的性质。

stl_config.h

STL配置参数

stl_alloc.h (<memory>)

// 一级内存分配器,当所需要内存大于128字节时调用该分配器,该类中的函数都是静态成员函数            
template<int __inst>   
class __malloc_alloc_template;

内存分配和释放调用的是C标准库`malloc``realloc``free`,当使用`malloc``realloc`内存分配失败时(一般是内存不足),会调用用户自己设置的内存处理函数`__malloc_alloc_oom_handler`释放内存,已得到足够的空闲内存区域,然后重新使用`malloc`, `realloc`重新尝试申请分配内存。如果用户没有设置内存处理函数,那么直接抛出错误。其中,`__malloc_alloc_oom_handler`是通过成员函数`  static void (* __set_malloc_handler(void (*__f)()))()`设置的,该函数返回以前的内存处理函数。      

// 内存分配类适配器       
template<class _Tp, class _Alloc>
class simple_alloc;

typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;
typedef malloc_alloc single_client_alloc;

// 内存分配适配器, 该适配器会多分配一段内存区域用于保存所分配的内存大小n,因此要保证该段内存区域足够大,以能够存储(size_t)n,一般保证大于sizeof(size_t)即可。n会用于reallocate与deallocate校验内存大小     
template <class _Alloc>
class debug_alloc;        

debug_alloc有一个私有数据成员enum {_S_extra = 8},来用于多分配一段_S_extra大小的内存区域,该内存区域是在所分配的总内存区域的首部。其中,n(类型size_t)是存在该段内存区域中的。
// 二级内存分配器,当分配的内存区域小于128字节时,会使用该内存分配器。使用16个链表维护一组内存区域,对应链表维护的内存大小对应为8,16,...,128。另外,该内是线程安全的。没分配一个对应大小的内存,就插入相应的链表中。      
enum {_ALIGN = 8};       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值