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 };

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 }

该迭代器还重载了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); }    

有了__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 }

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 }

下面就剩下插入跟删除元素的实现了,首先看看关于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 }

上面留下的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 }      

而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 }

 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 // 然后修改原来位置的值

时间: 2024-08-29 19:39:27

STL源码剖析(deque)的相关文章

STL 源码剖析 deque实现源码

#ifndef _HJSTL_DEQUE_H_ #define _HJSTL_DEQUE_H_ /* * Author:hujian * Time:2016/4/28 * discription:this file is about deque structure. * */ #include "hjstl_alloc.h" #include "hjstl_construct.h" #include "hjstl_iterator.h" #inc

STL&quot;源码&quot;剖析-重点知识总结

STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.search.copy.erase.从实现的角度来看,STL算法

通读《STL源码剖析》之后的一点读书笔记

[QQ群: 189191838,对算法和C++感兴趣可以进来] 直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adaptors).算法(algorithms).仿函数(functors)六个部分. 迭代器和泛型编程的思想在这里几乎用到了极致.模板或者泛型编程其实就是算法实现时不指定具体类型,而由调用的时候指定类型,进行特化.在STL中,迭代器保证了ST

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

【转载】STL&quot;源码&quot;剖析-重点知识总结

原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合套用: 容器(Containers):各种数据结构,如:vector.list.deque.set.map.用来存放数据.从实现的角度来看,STL容器是一种class template. 算法(algorithms):各种常用算法,如:sort.se

《STL源码剖析》---stl_deque.h阅读笔记(1)

双端队列deque是容器的一种.它比vector更强大,vector只可以在尾端插入元素,deque不只是可以再尾端插入,也可以在队列头插入.下面借助<STL源代码剖析>的图片讲解. 1.deque的内存结构 vector是开辟一段连续的内存,deque可以在前端插入元素,如果像vector开辟一段连续的内存,向前面插入元素不易维护.例如如果只是开辟一段连续内存,那么前端到开辟内存的起始位置要空多少个位置?当这段内存满了时,拷贝到更大内存时,前端空留多少位置?显然难以维护.deque在STL中

《STL源码剖析》---stl_deque.h阅读笔记(2)

看完,<STL源码剖析>---stl_deque.h阅读笔记(1)后,再看代码: G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_deque.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation fo

STL源码剖析之组件

本篇文章开始,进行STL源码剖析的一些知识点,后续系列笔记全是参照<STL源码剖析>进行学习记录的 STL在现在的大部分项目中,实用性已经没有Boost库好了,毕竟STL中仅仅提供了一些容器供编码者实用,Boost库相对而言全面了许多,所以更适合做一些项目的开发.但STL源码中依然有很多我们值得学习,思考的地方,包括现在大部分面试,都会问及到STL的框架源码部分.所以决定将这本书其中重要的几个部分拉出来做个笔记,以加深记忆并和大神们探讨探讨. 先简单介绍一下STL中的大致组成部分,一张图就明了

STL源码剖析 读书总结

<<STL源码剖析>> 侯捷著 很早就买了这本书, 一直没看, 现在在实验室师兄代码的时候发现里面使用了大量泛型编程的内容, 让我有了先看看这本书的想法. 看之前我对于泛型编程了解甚少, STL倒使用的比较熟练. 看完这本书之后, 只能表示以前对于STL的使用真是跟小孩玩似得, 只懂其冰山一角. 在真正的深入到源码之后, 对于STL中不容易理解的部分如 迭代器(iterator), 仿函数(functor), 配接器(adapter)才有了一个彻彻底底的了解, 这种东西不看源码光看