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 };
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 // 然后修改原来位置的值
以上是关于STL源码剖析(deque)的主要内容,如果未能解决你的问题,请参考以下文章