STL原始碼分析之deque有序容器 中
阿新 • • 發佈:2018-12-08
前言
前一節我們分析了deque
的基本使用, 本節我們來分析一下deque
的對map
的操作, 即插入, 刪除等. 但是本節只分析push, pop和刪除操作, 而insert
操作有點複雜還是放到下節來分析.
push, pop
因為deque
的是能夠雙向操作, 所以其push和pop操作都類似於list
都可以直接有對應的操作. 需要注意的是list
是連結串列, 並不會涉及到界線的判斷, 而deque
是由陣列來儲存的, 就需要隨時對界線進行判斷.
push實現.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
...
public: // push_* and pop_*
// 對尾進行插入
// 判斷函式是否達到了陣列尾部. 沒有達到就直接進行插入
void push_back(const value_type& t) {
if (finish.cur != finish.last - 1) {
construct(finish.cur, t);
++finish.cur;
}
else
push_back_aux(t);
}
// 對頭進行插入
// 判斷函式是否達到了陣列頭部. 沒有達到就直接進行插入
void push_front(const value_type& t) {
if (start.cur != start.first) {
construct(start.cur - 1, t);
--start.cur;
}
else
push_front_aux(t);
}
...
};
如果判斷陣列越界, 就移動到另一個數組進行push操作.
注意 : push_back
是先執行構造在移動node, 而push_front
finish
是指向最後一個元素的後一個地址而first
指向的就只第一個元素的地址. 下面pop也是一樣的.
// Called only if finish.cur == finish.last - 1.
// 到達了陣列的尾部
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_back_aux(const value_type& t) {
value_type t_copy = t;
reserve_map_at_back();
// 申請空間
*(finish.node + 1) = allocate_node();
__STL_TRY {
// 執行構造
construct(finish.cur, t_copy);
// 移動node, 指向下一個陣列的頭
finish.set_node(finish.node + 1);
finish.cur = finish.first; // cur只指向當前陣列的頭
}
// 如果分配失敗, 釋放掉該記憶體
__STL_UNWIND(deallocate_node(*(finish.node + 1)));
}
// Called only if start.cur == start.first.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t) {
value_type t_copy = t;
reserve_map_at_front();
// 申請空間
*(start.node - 1) = allocate_node();
__STL_TRY {
// 先要移動node, 讓其指向上一個陣列的尾部
start.set_node(start.node - 1);
// cur指向當前陣列的尾部
start.cur = start.last - 1;
// 執行構造
construct(start.cur, t_copy);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
start.set_node(start.node + 1);
start.cur = start.first;
deallocate_node(*(start.node - 1));
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
}
pop實現.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
...
public:
// 對尾部進行操作
// 判斷是否達到陣列的頭部. 沒有到達就直接釋放
void pop_back() {
if (finish.cur != finish.first) {
--finish.cur;
destroy(finish.cur);
}
else
pop_back_aux();
}
// 對頭部進行操作
// 判斷是否達到陣列的尾部. 沒有到達就直接釋放
void pop_front() {
if (start.cur != start.last - 1) {
destroy(start.cur);
++start.cur;
}
else
pop_front_aux();
}
...
};
pop判斷越界後執行以下函式.
// Called only if finish.cur == finish.first.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>:: pop_back_aux() {
deallocate_node(finish.first); // 先呼叫解構函式
finish.set_node(finish.node - 1); // 再移動node
finish.cur = finish.last - 1; // 然後cur指向當前陣列的最後位置
destroy(finish.cur); // 最後釋放記憶體空間.
}
// Called only if start.cur == start.last - 1. Note that if the deque
// has at least one element (a necessary precondition for this member
// function), and if start.cur == start.last, then the deque must have
// at least two nodes.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::pop_front_aux() {
destroy(start.cur); // 先釋放記憶體空間.
deallocate_node(start.first); // 再呼叫解構函式
start.set_node(start.node + 1); // 然後移動node
start.cur = start.first; // 最後cur指向當前陣列的第一個位置
}
reserve_map_at一類函式. pop和push都先呼叫了reserve_map_at_XX函式, 這些函式主要是為了判斷前後空間是否足夠.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
...
public:
void new_elements_at_front(size_type new_elements);
void new_elements_at_back(size_type new_elements);
void destroy_nodes_at_front(iterator before_start);
void destroy_nodes_at_back(iterator after_finish);
protected: // Allocation of map and nodes
// Makes sure the map has space for new nodes. Does not actually
// add the nodes. Can invalidate map pointers. (And consequently,
// deque iterators.)
// 始終保證後面要有一個及以上的空陣列大小
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 > start.node - map)
reallocate_map(nodes_to_add, true);
}
void reallocate_map(size_type nodes_to_add, bool add_at_front);
...
};
reallocate_map函式, 空間不足
- deque空間實際足夠
- deque內部進行調整start, 和finish
- deque空間真的不足
- 申請更大的空間
- 拷貝元素過去
- 修改map和start, finish指向
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add, bool add_at_front)
{
// 儲存現在的空間大小和新的空間大小
size_type old_num_nodes = finish.node - start.node + 1;
size_type new_num_nodes = old_num_nodes + nodes_to_add;
map_pointer new_nstart;
// map_size > 2 * new_num_nodes 發現deque空間還很充足就只是調整deque內部的元素就行了, 沒必要重新開空間
// 這種情況主要出現在一直往首或尾單方向插入元素, 導致首(尾)前面還有很多餘留的空間, 這種情況就這樣調整
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)
copy(start.node, finish.node + 1, new_nstart);
else
copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
}
// 空間是真的不夠了
else {
size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;
// 分配空間. 重新定位start的位置
map_pointer new_map = map_allocator::allocate(new_map_size);
new_nstart = new_map + (new_map_size - new_num_nodes) / 2 + (add_at_front ? nodes_to_add : 0);
// 拷貝原deque元素, 最後釋放掉原記憶體空間
copy(start.node, finish.node + 1, new_nstart);
map_allocator::deallocate(map, map_size);
// 調整map
map = new_map;
map_size = new_map_size;
}
// 重新調整start, finish
start.set_node(new_nstart);
finish.set_node(new_nstart + old_num_nodes - 1);
}
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::destroy_nodes_at_front(iterator before_start) {
for (map_pointer n = before_start.node; n < start.node; ++n)
deallocate_node(*n);
}
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::destroy_nodes_at_back(iterator after_finish) {
for (map_pointer n = after_finish.node; n > finish.node; --n)
deallocate_node(*n);
}
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
...
public:
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 - 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 + difference_type(n);
}
...
};
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::new_elements_at_front(size_type new_elements) {
size_type new_nodes = (new_elements + buffer_size() - 1) / buffer_size();
reserve_map_at_front(new_nodes);
size_type i;
__STL_TRY {
for (i = 1; i <= new_nodes; ++i)
*(start.node - i) = allocate_node();
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
for (size_type j = 1; j < i; ++j)
deallocate_node(*(start.node - j));
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
}
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::new_elements_at_back(size_type new_elements) {
size_type new_nodes = (new_elements + buffer_size() - 1) / buffer_size();
reserve_map_at_back(new_nodes);
size_type i;
__STL_TRY {
for (i = 1; i <= new_nodes; ++i)
*(finish.node + i) = allocate_node();
}
# ifdef __STL_USE_EXCEPTIONS
catch(...) {
for (size_type j = 1; j < i; ++j)
deallocate_node(*(finish.node + j));
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
}
刪除操作
不知道還記得我們最開始建構函式呼叫create_map_and_nodes
考慮到deque
實現前後插入時間複雜度為O(1), 保證了在前後留出了空間, 所以push和pop都可以在前面的陣列進行操作.
好了, 現在就來看erase. 因為deque的是由陣列構成, 所以地址空間是連續的. 刪除也就像vector
一樣, 要移動所有的元素, deque
為了保證效率儘量高, 就判斷刪除的位置是中間偏後還是中間偏前來進行移動.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
...
public: // Erase
iterator erase(iterator pos)
{
iterator next = pos;
++next;
difference_type index = pos - start;
// 刪除的地方是中間偏前, 移動前面的元素
if (index < (size() >> 1))
{
copy_backward(start, pos, next);
pop_front();
}
// 刪除的地方是中間偏後, 移動後面的元素
else {
copy(next, finish, pos);
pop_back();
}
return start + index;
}
// 範圍刪除, 實際也是呼叫上面的erase函式.
iterator erase(iterator first, iterator last);
void clear();
...
};
erase(iterator first, iterator last)
template <class T, class Alloc, size_t BufSize>
deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::erase(iterator first, iterator last)
{
if (first == start && last == finish) {
clear();
return finish;
}
else {
// 計算出兩個迭代器的距離, 畢竟是連續的, 可以直接計算
difference_type n = last - first;
// 同樣, 選擇前後哪種方法移動.
difference_type elems_before = first - start;
// 刪除的地方是中間偏前, 移動前面的元素
if (elems_before < (size() - n) / 2) {
copy_backward(start, first, last);
iterator new_start = start + n;
destroy(start, new_start);
// 可能會涉及到跨陣列的問題(使用者使用並不知道)
for (map_pointer cur = start.node; cur < new_start.node; ++cur)
data_allocator::deallocate(*cur, buffer_size());
start = new_start;
}
// 刪除的地方是中間偏後, 移動後面的元素
else {
copy(last, finish, first);
iterator new_finish = finish - n;
destroy(new_finish, finish);
// 可能會涉及到跨陣列的問題(使用者使用並不知道)
for (map_pointer cur = new_finish.node + 1; cur <= finish.node; ++cur)
data_allocator::deallocate(*cur, buffer_size());
finish = new_finish;
}
return start + elems_before;
}
}
clear函式. 刪除所有元素. 分兩步執行:
- 從第二個陣列開始到倒數第二個陣列一次性全部刪除, 畢竟中間的陣列肯定都是滿的, 前後兩個陣列就不一定是填充滿的.
- 刪除前後兩個陣列的元素
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::clear() {
// 從第二個陣列開始到倒數第二個陣列一次性全部刪除
// 畢竟中間的陣列肯定都是滿的, 前後兩個陣列就不一定是填充滿的.
for (map_pointer node = start.node + 1; node < finish.node; ++node) {
destroy(*node, *node + buffer_size());
data_allocator::deallocate(*node, buffer_size());
}
// 刪除前後兩個陣列的元素.
if (start.node != finish.node) {
destroy(start.cur, start.last);
destroy(finish.first, finish.cur);
data_allocator::deallocate(finish.first, buffer_size());
}
else
destroy(start.cur, finish.cur);
finish = start;
}
swap
deque
的swap操作也只是交換了start, finish, map, 並沒有交換所有的元素.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
...
void swap(deque& x)
{
__STD::swap(start, x.start);
__STD::swap(finish, x