一.deque的数据结构
1.1结构图
deque中主要包括一个中控器_M_map,首尾迭代器_M_start,_M_finish,两种型别的内存配置器 typedef simple_alloc<_Tp, _Alloc> _Node_alloc_type; typedef simple_alloc<_Tp*, _Alloc> _Map_alloc_type。其结构图如下
1.2 代码设计
接下来结合代码和上面的结构图来具体说明deque的数据结构实现。deque的主体结构如下:
template <class _Tp, class _Alloc>
class _Deque_base {
public:
typedef _Deque_iterator<_Tp,_Tp&,_Tp*> iterator;
typedef _Deque_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
_Deque_base(const allocator_type&, size_t __num_elements)
: _M_map(0), _M_map_size(0), _M_start(), _M_finish() {
_M_initialize_map(__num_elements); // 根据元素个数初始化中控器,在后面会具体介绍
}
...
protected:
_Tp** _M_map; // 指向中控器的起始位置
size_t _M_map_size; // 中控器大小
iterator _M_start; // 首部迭代器
iterator _M_finish; // 尾部迭代器
typedef simple_alloc<_Tp, _Alloc> _Node_alloc_type; //用于为缓冲节点的内存配置器
typedef simple_alloc<_Tp*, _Alloc> _Map_alloc_type; //用于为中控器分配内存的内存配置器
...
};
emplate <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class deque : protected _Deque_base<_Tp, _Alloc> { // 继承了_Deque_base
public: // Basic accessors
deque(const value_type* __first, const value_type* __last, const allocator_type& __a = allocator_type())
: _Base(__a, __last - __first)
{ uninitialized_copy(__first, __last, _M_start); }
iterator begin() { return _M_start; }
iterator end() { return _M_finish; }
const_iterator begin() const { return _M_start; }
const_iterator end() const { return _M_finish; }
//还实现了插入删除等操作,会在后面说明
private:
};
二.deque的内存管理
2.1 内存的初始化
我们先从deque的构造函数说起吧,如1.2中的源码所示,deque继承了_Deque_base,因此先调用了_Deque_base的构造函数,在其中调用了_M_initialized_map(),在该函数中首先分配了中控器_M_map的内存区,接着根据要放入的元素数量__num_elements,选择中控器中间的__num_elements / 缓冲区大小(以sizeof(_Tp)为单位) + 1个节点分配缓冲区内存,这个分配是调用_Node_alloc_type配置器实现的。其源码如下:
template <class _Tp, class _Alloc>
void _Deque_base<_Tp,_Alloc>::_M_initialize_map(size_t __num_elements)
{
size_t __num_nodes =
__num_elements / __deque_buf_size(sizeof(_Tp)) + 1;// 需要多少个缓冲区,若刚好整除会多配置一个,
//此时的_M_finish会指向多配置的这个节点,其中的_M_cur指向该缓冲区的首部
_M_map_size = max((size_t) _S_initial_map_size, __num_nodes + 2);// 中控器最小节点数为8
_M_map = _M_allocate_map(_M_map_size); // 调用内存配置器分配中控器map的内存空间
// 保证初始化时使用的中控节点位于map的最中间,这可使两端的可阔空间一样大
_Tp** __nstart = _M_map + (_M_map_size - __num_nodes) / 2; // 指向被使用的起始中控节点
_Tp** __nfinish = __nstart + __num_nodes; // 指向被使用的中控节点尾部
_M_create_nodes(__nstart, __nfinish); // 为中控节点[__nstart, __nfinish)创建缓冲区
_M_start._M_set_node(__nstart); // 首部迭代器回指_M_map中指向首部缓冲区的节点
_M_finish._M_set_node(__nfinish - 1); // 回指_M_map中的节点
_M_start._M_cur = _M_start._M_first; // 首部迭代器的_M_cur指向第一个缓冲区的首字节
_M_finish._M_cur = _M_finish._M_first + __num_elements % __deque_buf_size(sizeof(_Tp));
// 尾部迭代器的_M_cur最后一个元素的后一个位置
}
template <class _Tp, class _Alloc>
void _Deque_base<_Tp,_Alloc>::_M_create_nodes(_Tp** __nstart, _Tp** __nfinish)
{
_Tp** __cur;
try{
for (__cur = __nstart; __cur < __nfinish; ++__cur) //为中控节点[__nstart, __nfinish)创建缓冲区
*__cur = _M_allocate_node();
}
cache(...)(_M_destroy_nodes(__nstart, __cur)); // 若非配置失败则收回以分配的缓冲区,保证了分配的原子性
}
当在_Deque_base构造完成后,内存区的分配工作也已完毕,最后在deque的构造函数中,在分配的缓冲区内存上调用uninitialize_copy或uninitialized_fill来构造对象。至此,deque的构造过程结束,构造完成后的结构图如1.1节所示。
2.2 添加与删除
1. 尾部或首部添加元素
if( 尾部或首部还有两个及以上的剩余空间 ) {
调用构造函数在首部或尾部空间构造对象;
}
else {// 空间不足两个
if(_M_map中尾部的剩余空间不足两个及以上) {
// 调整或扩充_M_map
if(若_M_map的当前大小大于添加元素后使用的节点数) {
则不对_M_map进行扩充,而是将使用节点调整至_M_map的中间区域
}
else{
调用内存配置器为_M_map开辟一个更大的内存;
将_M_map中使用的节点拷贝到新的新的内存区;
}
}
在缓冲区的下一个(前一个)可用节点上调用构造函数
}
2. 尾部或首部删除元素
if(尾部迭代器所指缓冲区中所剩元素大于等于1) { // 通过迭代器中的_M_cur != _M_first判断
析构掉最后一个元素,并移动_M_finish->_M_cur;
}
else{ // 该缓冲区已无元素
销毁掉该缓冲区;
将_M_finish移动到前一个缓冲区,并析构掉最后一个元素
}
3.在中间删除元素
if(删除点之前的元素较少) {
调用copy_backward将元素后移;
再调用pop_front将最前面的一个元素移出;
}
else { // 删除点之后的元素较少
调用copy将删除点之后的元素前移;
调用pop_back将最后的一个元素移出;
}
4.在中间添加元素
if(插入点之前的元素较少) {
调用push_front(front())根据deque最前面的对象创建一个副本放入最前面;
调用copy将从_M_start + 2开始到插入点位置,调用copy函数进行前移;
}
else{//插入点之后的元素较少
调用push_back(back())将最后一个元素的副本放入末尾;
调用copy_backward将插入点之后的元素后移;
}
最后将带插入元素放入插入点