本博客中涉及到的所有代码均在我的github上存放,地址:mySTL
如果有兴趣的话可以去下载查看,注释比较详尽。
说点什么
相信大家如果对C++有一定的了解的话,都会知道C++
中有STL
这个超大的模版库,这个模版库提供了非常多的模版容器和模版算法,如常用的vector
、list
、stack
、queue
、map
、set
等等容器,sort
、find_if
、find
、swap
等模版函数
这个库由于创建时间过长、版本更替过多,现在也越来越臃肿了,导致我们想要去探究其源码的难度也越来越高(比如很多的编译时的宏和各种千奇百怪的性能提升手段),但是千变万变,这些牺牲代码可读性的措施都是为了提高算法和容器的效率或者说空间利用率
不过其实质的原理还是差不多的,比如各种空间构造和内存存储方式大致上都是差不多的,但具体到很细节的东西,各个版本就有各个版本的不同
比如这次我们要介绍的deque
,就是我根据比较易懂的版本自行写出的代码,所以注释还是比较充足的。
首先还是惯例,先介绍一下deque
吧
deque
的基本介绍
std::deque
( double-ended queue ,双端队列)是可以进行下标访问的顺序容器,它允许在其首尾两端快速插入及删除元素。
另外,在 deque
任一端插入或删除元素不会让当前正在使用的迭代器失效,至于原因,这个就和deque
的实现方式有很大的关系了。
与 std::vector
相反, deque
的元素不一定是相邻存储的:典型实现是采用单独分配的固定大小的相邻元素数组,外加一个额外的数组去存储这些空间的首地址,这表示下标访问必须进行二次指针解引用,与之相比 vector
的下标访问只需要进行一次。
deque
的存储按需自动扩展及收缩。扩张 deque
比扩展 std::vector
的消耗要少,因为它不涉及到复制当前的元素到新内存位置。但是同时, deque
也因此拥有较大的最小内存开销。就算只保有一个元素的 deque
也必须为其分配至少一个内存数组(例如 64 位 libstdc++
上为对象大小的 8 倍空间; 64 位 libc++
上为对象大小的16 倍或 4096 字节的较大者)。
deque
上常见操作的复杂度(效率)如下:
- 随机访问——常数 O(1)
- 在结尾或起始插入或移除元素——常数 O(1)
- 插入或移除元素——线性 O(n)
deque
的内存布局
其主要的内存布局大致如图:
通过上面的空间构造,我们可以知道,我们在使用deque
的的元素操作的时候(比如[ ]
操作,或者迭代器的++
操作的时候,并不是简单的将其指针+1或者+n ),所以我们需要进行更加细致的设计和安排,为deque
设计一个特化的、合适的迭代器,以及设计和其对应的空间配置方案
deque
需要维护一个指向各个连续内存空间的指针数组,所以它的迭代器势必也要能够判断各个连续空间的边界以便于知道自己该如何进行++
和- -
,下面将会对这个迭代器进行一定的代码分析。
deque
最重要的就是其空间不足进行重新配置的过程,了解了这个,我们差不多就完全理解了deque
是个怎样的东西
我们边看代码边说吧,毕竟网上关于deque
的内存布局的东西已经说的太多了
以下代码仅供参考,不具备实际操作的意义。
deque
的类定义
template<
class T,
class Allocator = std::allocator<T>
> class deque;
以上就是一个典型的deque
的头部
T
必须满足可复制赋值 (CopyAssignable) 和可复制构造 (CopyConstructible) 的要求。
Allocator
用于获取/释放内存及构造/析构内存中元素的分配器。类型必须满足分配器 (Allocator
) 的要求。若Allocator::value_type
与T
不同则行为未定义。
以下是我自己写的deque
的头文件代码(说是头文件,实质上模版类只含有头文件,其定义我写在了另外一个头文件中#include"Deque_detail.h"
),其完整头文件代码如下:
#ifndef _DEQUE_H_
#define _DEQUE_H_
#include"Allocator.h"
#include"Iterator.h"
#include"Algorithm.h"
#include"UninitializedFunc.h"
namespace STL {
template<class T, class Alloc = allocator<T>>
class deque;
//deque_iterator detail
namespace Detail {
template<class T>
class deque_iterator :public iterator<random_access_i