STL源码剖析(deque)

Posted Runnyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL源码剖析(deque)相关的知识,希望对你有一定的参考价值。

deque是一个双向开口的容器,在头尾两端进行元素的插入跟删除操作都有理想的时间复杂度。

deque使用的是分段连续线性空间,它维护一个指针数组(T** map),其中每个指针指向一块连续线性空间。

(map左右两边一般留有剩余空间,用于前后插入元素,具体下面可以看到其实现)

 

根据上图,可以了解到deque的迭代器的基本定义。

 1 template <class T, class Ref, class Ptr, size_t BufSiz>
 2 struct __deque_iterator {
 3     // 基本型别的定义
 4     typedef __deque_iterator<T, T&, T*, BufSiz>             iterator;
 5     typedef random_access_iterator_tag iterator_category;
 6     typedef T value_type;
 7     typedef Ptr pointer;
 8     typedef Ref reference;
 9     typedef size_t size_type;
10     typedef ptrdiff_t difference_type;
11     typedef T** map_pointer;
12     typedef __deque_iterator self;
13 
14     // 缓冲区的大小
15     tatic size_t buffer_size() { ... }    
16 
17     // 主要维护的三个指针
18     T* cur;    // 指向当前元素
19     T* first;   // 指向当前缓冲区的头
20     T* last;   // 指向当前缓冲区的尾
21 
22     map_pointer node; // 指向当前缓冲区在map中的位置
23     // ...
24 };
View Code

 

deque的实现基本都是依赖于其迭代器的实现(主要是各种操作符的重载)

 1 // 用于跳到下一个缓冲区
 2 void set_node(map_pointer new_node) {
 3     node = new_node;
 4     first = *new_node;
 5     last = first + difference_type(buffer_size());
 6 }
 7 
 8 reference operator*() const { return *cur; }
 9 pointer operator->() const { return &(operator*()); }
10 
11 self& operator++() {
12     ++cur;
13     if (cur == last) {  // 到达缓冲区尾端
14         set_node(node + 1);
15         cur = first;
16     }
17     return *this; 
18 }
19 
20 self& operator--() {
21     if (cur == first) { // 已到达缓冲区头端
22         set_node(node - 1);
23         cur = last;
24     }
25     --cur;
26     return *this;
27 }
28 
29 // 迭代器之间的距离(相隔多少个元素)
30 difference_type operator-(const self& x) const {
31     return difference_type(buffer_size()) * (node - x.node - 1) +
32       (cur - first) + (x.last - x.cur);
33 }
View Code

该迭代器还重载了operator+=、operator+、operator-=、operator-(difference_type)等,

都是通过set_node()跟调整cur、first、last、node成员来实现。同时重载的operator[]使用operator+来进行随机存取。

 1 self& operator+=(difference_type n) {
 2     difference_type offset = n + (cur - first);
 3     if (offset >= 0 && offset < difference_type(buffer_size()))
 4         cur += n;
 5     else {
 6         // 目标在不同的缓冲区
 7         difference_type node_offset =
 8         offset > 0 ? offset / difference_type(buffer_size())
 9                    : -difference_type((-offset - 1) / buffer_size()) - 1;
10         // 跳到相应的缓冲区
11         set_node(node + node_offset);
12         // 调整cur指针
13         cur = first + (offset - node_offset * difference_type(buffer_size()));
14     }
15     return *this;
16 }
17 
18 // 下面的都直接或间接的调用operator+=
19 self operator+(difference_type n) const {
20     self tmp = *this;
21     return tmp += n;
22 }
23 
24 self& operator-=(difference_type n) { return *this += -n; }
25  
26 self operator-(difference_type n) const {
27     self tmp = *this;
28     return tmp -= n;
29 }
30 
31 reference operator[](difference_type n) const { return *(*this + n); }    
View Code

 

有了__deque_iterator,deque的基本实现就比较简单了(主要维护start、finish这两个迭代器)

下面是deque的基本定义

 1 template <class T, class Alloc = alloc, size_t BufSiz = 0> 
 2 class deque {
 3 public:                         
 4     typedef T value_type;
 5     typedef value_type* pointer;
 6     typedef size_t size_type;
 7     typedef pointer* map_pointer;
 8 public:
 9     typedef __deque_iterator<T, T&, T*, BufSiz>  iterator;
10 protected:
11     iterator start;    // 第一个节点
12     iterator finish;   // 最后一个结点
13 
14     map_pointer map;
15     size_type map_size;  
16 public:
17     iterator begin() { return start; }
18     iterator end() { return finish; }
19     
20     reference operator[](size_type n) { return start[difference_type(n)]; } // 调用迭代器重载的operator[]
21    
22     // ...
23 }
View Code

deque的constructor会调用create_map_and_nodes()来初始化map

 1 // 每次配置一个元素大小的配置器
 2 typedef simple_alloc<value_type, Alloc> data_allocator;
 3 // 每次配置一个指针大小的配置器
 4 typedef simple_alloc<pointer, Alloc> map_allocator;
 5 
 6 template <class T, class Alloc, size_t BufSize>
 7 void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
 8     // 需要分配的结点数  如果为能整除 则多分配多一个结点
 9     size_type num_nodes = num_elements / buffer_size() + 1;
10 
11     // 分配结点内存 (前后预留一个 用于扩充)
12     map_size = max(initial_map_size(), num_nodes + 2);
13     map = map_allocator::allocate(map_size);
14 
15     // 将需要分配缓冲区的结点放在map的中间
16     map_pointer nstart = map + (map_size - num_nodes) / 2;
17     map_pointer nfinish = nstart + num_nodes - 1;
18     
19     map_pointer cur;
20     // 为了简化 去掉了异常处理的代码
21     for (cur = nstart; cur <= nfinish; ++cur)
22         *cur = allocate_node(); // 为每个结点分配缓冲区
23     }
24 
25     // 设置start、finish指针
26     start.set_node(nstart);
27     finish.set_node(nfinish);
28     start.cur = start.first;
29     finish.cur = finish.first + num_elements % buffer_size();
30 }
View Code

 

下面就剩下插入跟删除元素的实现了,首先看看关于push_front()的操作的实现。

 1 void push_front(const value_type& t) {
 2     if (start.cur != start.first) {    // 第一缓冲区还有容量
 3         construct(start.cur - 1, t);  
 4         --start.cur;
 5     }
 6     else
 7         push_front_aux(t);
 8   }
 9 
10 // 如果第一缓冲区容量不足会调用这个函数来配置新的缓冲区
11 template <class T, class Alloc, size_t BufSize>
12 void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t) {
13     value_type t_copy = t;
14     reserve_map_at_front();   // 可能导致map的重新整治
15     *(start.node - 1) = allocate_node();
16     start.set_node(start.node - 1);
17     start.cur = start.last - 1;
18     construct(start.cur, t_copy);
19 } 
20 
21 // 根据map前面为分配的结点数量来判断是否需要重新整治
22 void reserve_map_at_front (size_type nodes_to_add = 1) {
23     if (nodes_to_add > start.node - map)
24         reallocate_map(nodes_to_add, true);
25 }
View Code

上面留下的reallocate_map函数执行如下功能:

1.如果map中空闲指针足够多,则将已分配的结点移到map的中间。

2.否则重新分配一个map,将旧的map释放,把已分配的结点移到new_map的中间。

然后调整start跟finish迭代器。

 

然后是pop_front()的实现

 1 void pop_front() {
 2     if (start.cur != start.last - 1) { 
 3         destroy(start.cur);
 4         ++start.cur;
 5     }
 6     else 
 7         pop_front_aux();
 8 }
 9 
10 // 当前缓冲区只剩一个元素
11 template <class T, class Alloc, size_t BufSize>
12 void deque<T, Alloc, BufSize>::pop_front_aux() {
13     destroy(start.cur);
14     deallocate_node(start.first);  // 释放该缓冲区
15     start.set_node(start.node + 1);
16     start.cur = start.first;
17 }      
View Code

 

而push_back()跟pop_back()的实现跟上面的大同小异。

最后看看erase()跟insert()的实现

 1 iterator erase(iterator pos) {
 2     iterator next = pos;
 3     ++next;
 4     difference_type index = pos - start; // 迭代器的operator-
 5     if (index < (size() >> 1)) { // 如果清除点之前的元素比较少
 6         // 将清除点之前的所有元素后移一位  然后删除第一个元素
 7         copy_backward(start, pos, next);  // 利用了迭代器的operator--
 8         pop_front();
 9     }
10     else { // 如果清除点之后的元素比较少
11         // 将清除点之后的所有元素前移一位  然后删除最后一个元素
12         copy(next, finish, pos);  // 利用了迭代器的operator++
13         pop_back();
14     }
15     return start + index;
16 }
View Code
 1 iterator insert(iterator position, const value_type& x) {
 2     if (position.cur == start.cur) {
 3         // 插入位置为begin()
 4         push_front(x);
 5         return start;
 6     }
 7     else if (position.cur == finish.cur) {
 8         // 插入位置为end()
 9         push_back(x);
10         iterator tmp = finish;
11         --tmp;
12         return tmp;
13     }
14     else {
15         // 如果插入位置是在(begin(), end())
16         return insert_aux(position, x);
17     }
18 }
19 
20 // insert_aux()跟erase()实现类似
21 // 调用copy()或者copy_backward()将元素前移或者后移
22 // 然后修改原来位置的值
View Code

 

以上是关于STL源码剖析(deque)的主要内容,如果未能解决你的问题,请参考以下文章

STL源码剖析(deque)

重读STL源码剖析:deque

STL 源码剖析读书笔记四:序列式容器之 dequestackqueue

《STL源代码剖析》---stl_deque.h阅读笔记

《STL源代码剖析》---stl_deque.h阅读笔记

STL源码剖析(中文完整版)pdf