前言
STL 中,deque
是一种双向开口连续线性空间(逻辑上),deque
和 vector
的最大差异,一在于 deque
允许于常数时间内对起头端进行元素的插入或移除操作,二在于 deque
没有所谓容量 (capacity) 观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,功能十分强大;但对应的代价则是:deque
的迭代器与数据结构为了维持其 “整体连续” 的假象,被设计得异常复杂。
———— 《STL源码剖析》侯捷
本节将会分析 deque
的插入与删除操作。
map 及 buffer (node) 的管理
由于 deque
采用分段式存储的方式,将数据存放在分段的缓冲区 ( node ) 中,并使用中控器 ( map ) 进行管理。而 map
与每个 node
本身也是有容量的概念的,在插入操作时必然会引起 map
及 node
的变化,在分析插入操作之前让我们先来分析一下 deque
是如何管理 map
及 node
的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| template <class T> class deque<T> {
public:
pointer allocate_node() { return data_allocator::allocate(buffer_size); }
void deallocate_node(pointer p) { data_allocator::deallocate(p, buffer_size); }
map_pointer allocate_map(size_type n) { return map_allocator::allocate(n); }
void deallocate_map(map_pointer p, size_type n) { map_allocator::deallocate(p, n); }
void reserve_map_at_back(size_type nodes_to_add = 1) { if (nodes_to_add + 1 > map_size_ - (finish_.node - map_)) { reallocate_map(nodes_to_add, false); } }
void reserve_map_at_front(size_type nodes_to_add = 1) { if (nodes_to_add > static_cast<size_type>(start_.node - map_)) { reallocate_map(nodes_to_add, true); } }
void reallocate_map(size_type nodes_to_add, bool add_at_front);
iterator reserve_elements_at_front(size_type n) { size_type vacancies = start_.cur - start_.first; if (n > vacancies) { new_elements_at_front(n - vacancies); } return start_ - static_cast<difference_type>(n); } iterator reserve_elements_at_back(size_type n) { size_type vacancies = (finish_.last - finish_.cur) - 1; if (n > vacancies) { new_elements_at_back(n - vacancies); } return finish_ + static_cast<difference_type>(n); }
void new_elements_at_front(size_type new_elements); void new_elements_at_back(size_type new_elements);
}
|
这些函数负责分配及释放 map
与 node
的空间。其中,reallocate_map()
负责在 map
空间不足时重新分配 map
的大小,看一下 reallocate_map()
的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
template <class T> inline void deque<T>::reallocate_map(size_type nodes_to_add, bool add_at_front) { const size_type old_num_nodes = finish_.node - start_.node + 1; const size_type new_num_nodes = old_num_nodes + nodes_to_add;
map_pointer new_nstart;
if (map_size_ > 2 * new_num_nodes) { new_nstart = map_ + (map_size_ - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0); if (new_nstart < start_.node) { tinystl::copy(start_.node, finish_.node + 1, new_nstart); } else { tinystl::copy_backward(start_.node, finish_.node + 1, new_nstart + old_num_nodes); } } else { const size_type new_map_size = map_size_ + tinystl::max(map_size_, nodes_to_add) + 2; map_pointer new_map = allocate_map(new_map_size); new_nstart = new_map + (new_map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0); tinystl::copy(start_.node, finish_.node + 1, new_nstart); map_allocator::deallocate(map_, map_size_); map_ = new_map; map_size_ = new_map_size; }
start_.set_node(new_nstart); finish_.set_node(new_nstart + old_num_nodes - 1); }
|
reallocate_map
只是重新分配了 map
的大小,具体 map
中指向的每个 node
所需要的空间,还需要 new_elements_at_front()
与 new_elements_at_back()
以下函数负责分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
template <class T> inline void deque<T>::new_elements_at_front(size_type new_elements) { THROW_LENGTH_ERROR_IF(max_size() - size() < new_elements, "deque<T> too long"); const size_type new_nodes = (new_elements + buffer_size - 1) / buffer_size; reserve_map_at_front(new_nodes); size_type i; try { for (i = 1; i <= new_nodes; ++i) *(start_.node - i) = allocate_node(); } catch (...) { for (size_type j = 1; j < i; ++j) deallocate_node(*(start_.node - j)); throw; } }
template <class T> inline void deque<T>::new_elements_at_back(size_type new_elements) { THROW_LENGTH_ERROR_IF(max_size() - size() < new_elements, "deque<T> too long"); const size_type new_nodes = (new_elements + buffer_size - 1) / buffer_size; reserve_map_at_back(new_nodes); size_type i; try { for (i = 1; i <= new_nodes; ++i) *(finish_.node + i) = allocate_node(); } catch (...) { for (size_type j = 1; j < i; ++j) deallocate_node(*(finish_.node + j)); throw; } }
|
其中,allocate_node()
与 dellocate_node()
是两个用于分配与释放 node 空间的函数,就是简单地调用了 allocate
与 dellocate
,根据 deque_buf_size::value
来确定每个 node 要分配多少空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
template <class T> struct deque_buf_size { static constexpr size_t value = sizeof(T) < 256 ? 4096 / sizeof(T) : 16; };
static const size_type buffer_size = deque_buf_size<T>::value;
pointer allocate_node() { return data_allocator::allocate(buffer_size); }
void deallocate_node(pointer p) { data_allocator::deallocate(p, buffer_size); }
|
插入操作
deque
容器对外提供了许多插入操作的接口,这里我们挑其中的一部分讲解一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| template <class T> class deque<T> {
public:
template <class ...Args> void emplace_front(Args&& ...args);
template <class ...Args> void emplace_back(Args&& ...args);
template <class ...Args> iterator emplace(iterator pos, Args&& ...args);
void push_front(const value_type& value) { emplace_front(value); } void push_front(value_type&& value) { emplace_front(tinystl::move(value)); }
void push_back(const value_type& value) { emplace_back(value); } void push_back(value_type&& value) { emplace_back(tinystl::move(value)); }
template <class ...Args> void push_back_aux(Args&& ...args);
template <class ...Args> void push_front_aux(Args&& ...args); iterator insert(iterator pos, const value_type& value); iterator insert(iterator pos, value_type&& value); void insert(iterator pos, size_type n, const value_type& value); template <class InputIterator, typename std::enable_if< tinystl::is_input_iterator<InputIterator>::value, int>::type = 0> void insert(iterator pos, InputIterator first, InputIterator last) { insert_dispatch(pos, first, last, iterator_category(first)); }
}
|
emplace
& push
与 vector
相同,deque
也支持 emplace_back()
与 push_back()
两种不同的插入操作,关于二者的区别,已经在 上一篇博客 中说的十分清除了,这里就不再赘述。让我们直接看它们的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| template <class T> template <class ...Args> void deque<T>::emplace_front(Args&& ...args) { if (start_.cur != start_.first) { data_allocator::construct(start_.cur - 1, tinystl::forward<Args>(args)...); --start_.cur; } else { push_front_aux(tinystl::forward<Args>(args)...); } }
template <class T> template <class ...Args> void deque<T>::emplace_back(Args&& ...args) { if (finish_.cur != finish_.last - 1) { data_allocator::construct(finish_.cur, tinystl::forward<Args>(args)...); ++finish_.cur; } else { push_back_aux(tinystl::forward<Args>(args)...); } }
template <class T> template <class ...Args> typename deque<T>::iterator deque<T>::emplace(iterator pos, Args&& ...args) { if (pos.cur == start_.cur) { emplace_front(tinystl::forward<Args>(args)...); return start_; } else if (pos.cur == finish_.cur) { emplace_back(tinystl::forward<Args>(args)...); return finish_ - 1; } return insert_aux(pos, tinystl::forward<Args>(args)...); }
|
emplace()
直接在指定位置构造一个元素,如果该位置是首或尾的话直接调用对应的函数,否则需要使用 insert_aux()
进行处理。
在 emplace_back()
与 emplace_front()
中,如果相应位置的缓冲区仍然有空间,就直接构造;否则需要调用 push_xxx_aux()
先重新分配 map
再插入元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
template <class T> template <class... Args> inline void deque<T>::push_back_aux(Args &&...args) { reserve_map_at_back(); *(finish_.node + 1) = allocate_node(); try { data_allocator::construct(finish_.cur, tinystl::forward<Args>(args)...); finish_.set_node(finish_.node + 1); finish_.cur = finish_.first; } catch (...) { deallocate_node(*(finish_.node + 1)); throw; } }
template <class T> template <class... Args> inline void deque<T>::push_front_aux(Args &&...args) { reserve_map_at_front(); *(start_.node - 1) = allocate_node(); try { start_.set_node(start_.node - 1); start_.cur = start_.last - 1; data_allocator::construct(start_.cur, tinystl::forward<Args>(args)...); } catch (...) { ++start_; deallocate_node(*(start_.node - 1)); throw; } }
template <class T> template <class ...Args> typename deque<T>::iterator deque<T>::insert_aux(iterator pos, Args&& ...args) { const auto elem_before = pos - start_; if (elem_before < (size() >> 1)) { emplace_front(front()); auto front1 = start_; ++front1; auto front2 = front1; ++front2; pos = start_ + elem_before; auto pos1 = pos; ++pos1; tinystl::copy(front2, pos1, front1); } else { emplace_back(back()); auto back1 = finish_; --back1; auto back2 = back1; --back2; pos = start_ + elem_before; tinystl::copy_backward(pos, back2, back1); } *pos = value_type(tinystl::forward<Args>(args)...); return pos; }
|
在 insert_aux()
中,会根据插入位置前后元素的个数选择最优的移动方式。
insert
emplace
与 push
支持在指定位置插入一个元素,却不支持插入多个元素,这一任务由 insert
完成。插入多个元素的操作相对来说较为繁琐,这里仅介绍一部分内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template <class T> typename deque<T>::iterator deque<T>::insert(iterator pos, const value_type& value) { return emplace(pos, value); }
template <class T> typename deque<T>::iterator deque<T>::insert(iterator pos, value_type&& value) { return emplace(pos, tinystl::move(value)); }
template <class T> void deque<T>::insert(iterator pos, size_type n, const value_type& value) { if (pos.cur == start_.cur) { auto new_start = reserve_elements_at_front(n); tinystl::uninitialized_fill_n(new_start, n, value); start_ = new_start; } else if (pos.cur == finish_.cur) { auto new_finish = reserve_elements_at_back(n); tinystl::uninitialized_fill_n(finish_, n, value); finish_ = new_finish; } else { fill_insert(pos, n, value); } }
|
可以看出,insert
有多个重载,其中插入一个元素的操作都是直接调用 emplace()
;当插入多个元素时,如果位置在头部或尾部就直接使用全局函数 uninitialized_fill_n()
在指定位置填充元素,其他情况则需要调用 fill_insert()
进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
template <class T> void deque<T>::fill_insert(iterator pos, size_type n, const value_type& value) { const auto elem_before = pos - start_; const auto len = size(); auto value_copy = value;
if (elem_before < (len >> 1)) { auto old_start = start_; auto new_start = reserve_elements_at_front(n); pos = start_ + elem_before;
try { if (elem_before >= n) { auto start_n = start_ + static_cast<difference_type>(n); tinystl::uninitialized_copy(start_, start_n, new_start); start_ = new_start; tinystl::copy(start_n, pos, old_start); tinystl::fill(pos - static_cast<difference_type>(n), pos, value_copy); } else { auto mid = tinystl::uninitialized_copy(start_, pos, new_start); tinystl::uninitialized_fill(mid, start_, value_copy); start_ = new_start; tinystl::fill(old_start, pos, value_copy); } } catch (...) { if (new_start.node != start_.node) destroy_buffer(new_start.node, start_.node - 1); throw; }
} else { auto old_finish = finish_; auto new_finish = reserve_elements_at_back(n); const auto elem_after = len - elem_before; pos = finish_ - elem_after;
try { if (elem_after > n) { auto finish_n = finish_ - static_cast<difference_type>(n); tinystl::uninitialized_copy(finish_n, finish_, finish_); finish_ = new_finish; tinystl::copy_backward(pos, finish_n, old_finish); tinystl::fill(pos, pos + static_cast<difference_type>(n), value_copy); } else { tinystl::uninitialized_fill(finish_, pos + static_cast<difference_type>(n), value_copy); tinystl::uninitialized_copy(pos, finish_, pos + static_cast<difference_type>(n)); finish_ = new_finish; tinystl::fill(pos, old_finish, value_copy); } } catch (...) { if (new_finish.node != finish_.node) destroy_buffer(finish_.node + 1, new_finish.node); throw; } } }
|
fill_insert()
函数较为复杂,主要是要根据插入位置前后元素的个数来采取不同的移动策略,这一点在上面的 insert_aux()
中也有所体现。同时,还要根据 n
( 插入元素个数 ) 与 elems_before / back
( 之前或之后的元素个数 ) 之间的关系来进行不同的操作,因此会比较繁琐。但逻辑不是很复杂,画个图就能明白。
删除操作
deque
同样向外提供了一些负责删除元素的接口,以下也列举部分。
1 2 3 4 5 6 7 8 9 10 11
| template <class T> class deque<T> {
public: void pop_front(); void pop_back();
iterator erase(iterator pos); iterator erase(iterator first, iterator last); void clear(); }
|
pop
pop
的逻辑比较简单,只是要额外考虑是否要释放缓冲区的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template <class T> void deque<T>::pop_front() { TINYSTL_DEBUG(!empty()); if (start_.cur != start_.last - 1) { data_allocator::destroy(start_.cur); ++start_.cur; } else { data_allocator::destroy(start_.cur); ++start_; destroy_buffer(start_.node - 1, start_.node - 1); } }
template <class T> void deque<T>::pop_back() { TINYSTL_DEBUG(!empty()); if (finish_.cur != finish_.first) { --finish_.cur; data_allocator::destroy(finish_.cur); } else { --finish_; data_allocator::destroy(finish_.cur); destroy_buffer(finish_.node + 1, finish_.node + 1); } }
|
erase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| template <class T> typename deque<T>::iterator deque<T>::erase(iterator pos) { auto next = pos; ++next; const auto elem_before = pos - start_; if (elem_before < (size() >> 1)) { tinystl::copy_backward(start_, pos, next); pop_front(); } else { tinystl::copy(next, finish_, pos); pop_back(); } return start_ + elem_before; }
template <class T> typename deque<T>::iterator deque<T>::erase(iterator first, iterator last) { if (first == start_ && last == finish_) { clear(); return finish_; } else { const auto n = last - first; const auto elems_before = first - start_; if (elems_before < (size() - n) / 2) { tinystl::copy_backward(start_, first, last); auto new_start = start_ + n; data_allocator::destroy(start_.cur, new_start.cur); start_ = new_start; } else { tinystl::copy(last, finish_, first); auto new_finish = finish_ - n; data_allocator::destroy(new_finish.cur, finish_.cur); finish_ = new_finish; } return start_ + elems_before; } }
|
erase()
函数同样会根据前后元素个数的关系来确定最佳的移动方式,也有两个重载,分别用于消除指定位置与区间的元素。当传给 erase()
的参数恰好是整个 deque
的区间时,会调用 clear()
,clear()
的功能也已经在上一篇分析过,这里就不再赘述。