Analysis of stl source code 06-deque

One, deque overview

  • Grammar of deque: https://blog.csdn.net/qq_41453285/article/details/105483037
  • In general: a deque
  • Features:
    • Support fast random access (support index value)
    • Insert/delete at the beginning and end is fast
    • A deque is a very complex data structure, composed of multiple vectors , and the iterator will jump in different intervals when it is used.
    • When accessing elements, the internal structure of deque will have an indirect process, which is slower than vector operations
    • In systems with limited memory, deque can contain more elements than vector, because it uses more than one piece of memory
  • Design purpose: insert and delete elements at both ends of the head and tail respectively. Compared with vector, vector is too inefficient in the head operation

  • When to use: Need to insert and delete operations at both ends
  • The biggest difference with vector:
    • One is that deque allows the insertion or removal of elements at the beginning within a constant time
    • The second is that deque does not have the concept of capacity , because it is dynamically combined with segmented continuous spaces, and a new space can be added and linked at any time. In other words, things like "reconfigure a larger space due to insufficient old space, then copy elements, and release the old space" like vector does not happen in deque. Therefore, deque does not need to provide the so-called reserve function
  • Deque's iterator: Although deque also provides Ramdon Access Iterator, its iterator is not a native indicator , and its complexity is not comparable to vector (you will know when you see the source code later), which of course is affecting All computing levels. Therefore,  unless necessary, we should choose to use vector instead of deque as much as possible . For the deque sorting action, for the highest efficiency, the deque can be completely copied to a vector first, and then the vector is sorted (using the STL sort algorithm), and then copied back to the deque
  • Comparison with other containers:
vector Variable size array . Support fast random access. Inserting or deleting elements beyond the tail can be slow
and Deque . Support fast random access. Insert/delete at the beginning and end is fast
list Doubly linked list . Support two-way sequential access. Insertion and deletion at any position in the list are fast
forward_list Singly linked list . Only supports one-way sequential access. Insert and delete operations at any position in the linked list are fast
array Fixed size array . Support fast random access. Cannot add or delete elements
string A container similar to vector , but dedicated to storing characters. Random access is fast. Fast insertion or deletion at the tail

2. Deque's central controller (map) and buffer

  • A deque is composed of a section of quantitative continuous space :
    • Once it is necessary to add a new space at the front or end of the deque, configure a quantitative continuous space and connect it in series at the head or end of the entire deque.
    • The biggest task of deque is to maintain the illusion of overall continuity in these segmented quantitative continuous spaces and provide random access interfaces. Avoid the cycle of "reconfiguration, copy, release" at the cost of a complex iterator architecture
  • Affected by the literal effect of piecewise continuous linear space, we may think that the implementation complexity of deque is not as good as or far from that of vector, but it is not. The main reason is that in order to segment the continuous linear space, there must be central control. In order to maintain the illusion of overall continuity, the design of the data structure and the forward and backward actions of the iterator are quite cumbersome. The implementation code of deque is much more than that of vector or list.

map、map_size

  • deque uses a so-called map (note that it is not a STL map container) as the master
  • The so-called map here is a small continuous space, in which each element (here called ㆒ node, node) is a pointer , pointing to another (larger) continuous linear space, called a buffer
  • The buffer is the main storage space of the deque. SGI STL allows us to specify the buffer size, the default value of 0 means that 512 bytes buffer will be used
template <class T, class Alloc = alloc, size_t BufSiz = 0>

class deque {

public: //Basic types

    typedef T value_type;

    typedef value_type* pointer;

    ...


protected: //Internal typedefs

    //元素的指针的指针(pointer of pointer of T)

    typedef pointer* map_pointer;


protected: // Data members

    map_pointer map; //指向 map,map是块连续空间,其内的每个元素

    //都是一个指标(称为节点),指向一块缓冲区

    size_type map_size; //map内可容纳多少指针

    ...

};

 

  • Sorting out the numbing types of definitions (which are actually necessary for type safety), we can find that map is actually a T**, that is to say, it is a pointer, so What refers to is also an indicator, pointing to a space of type T, as shown in the figure below

  • Later in the deque construction process, I will explain in detail the configuration and maintenance of map
  • Remarks: The initial state of deque (without any elements) retains a buffer (it will be mentioned in the introduction of clear below)

Three, deque iterator

  • deque is a segmented continuous space. The task of maintaining the illusion of "whole continuity" falls on the iterator's operator++ and operator-- two operators
  • Let us think about what structure the deque iterator should have:
    • First, it must be able to point out where the segmented continuous space (ie buffer) is
    • Secondly, it must be able to judge whether it is already at the edge of the buffer zone where it is located. If so, it must jump to the next or previous buffer zone once it moves forward or backward
    • In order to be able to jump correctly, deque must keep abreast of the control center (map)
    • The following source code meets these requirements

cur、first、last、node、buffer_size()函数

  • cur: the element pointed to by the current iterator
  • first: the head of the buffer interval currently pointed to by the iterator
  • last: the end of the buffer interval currently pointed to by the iterator
  • node: a pointer to the deque controller. Used to point to which pointer in the controller the buffer pointed to by the current iterator is managed
  • buffer_size() function: absolute buffer size function, call __deque_buf_size() global function (see below)
template <class T, class Ref, class Ptr, size_t BufSiz>

struct deque_iterator { // 未继承 std::iterator


    typedef deque_iterator<T, T&, T*, BufSiz> iterator;

    typedef deque_iterator<T, const T&, const T*, BufSiz> const_iterator;

    static size_t buffer_size(){return deque_buf_size(BufSiz,sizeof(T));}


    // 未继承 std::iterator,所以必须自行撰写五个必要的迭代器相应型别

    typedef random_access_iterator_tag iterator_category; // (1)
    
    typedef T value_type; // (2)

    typedef Ptr pointer; // (3)

    typedef Ref reference; // (4)

    typedef size_t size_type;

    typedef ptrdiff_t difference_type; // (5)

    typedef T** map_pointer;

    typedef deque_iterator self;
    

    //保持与容器的联结

    T* cur; // 此迭代器所指之缓冲区中的现行(current)元素

    T* first; // 此迭代器所指之缓冲区的头

    T* last; // 此迭代器所指之缓冲区的尾(含备用空间)

    map_pointer node; // 指向管控中心

    ...

};

deque_buf_size() global function

  • The function buffer_size() in the iterator used to determine the buffer size directly calls deque_buf_size(). This is a global function, defined as follows:
    • If n is not 0 , return n, indicating that the buffer size is set by the user
    • If n is 0 , it means that the buffer size uses the default value, then:
      • If sz (element size, sizeof(value_type)) is less than 512, return 512/sz
      • If sz is not less than 512, return 1
  • Remarks: n refers to how many elements of data type T can be stored in the buffer, not the byte size of the buffer. For example, the type of data stored in a deque is intn, n=8, which means that the buffer can store 8 int values ​​(buffer size is 8), and the byte size of the buffer is 4*8=32bytes
inline size_t deque_buf_size(size_t n, size_t sz)

{

    return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));

}

Demonstration description

  • Suppose we declare a deque<int,alloc,8>, then the buffer size of this deque is 8 (meaning that the buffer can store 8 elements of type int), and the number of bytes in the buffer is 4*8=32bytes
  • Assuming that after some operations, the deque has 20 elements, then the two iterators returned by its begin() and end() should be as shown in the figure below . These two iterators have actually been kept in the deque, named start and finish, which can be seen in the deque data structure later
  • 20 elements require 20/8=3 buffers, so three nodes are used in the map
    • The cur indicator in the iterator start of course points to the first element of the buffer
    • The cur indicator in the iterator finish of course points to the last element (next position) of the buffer
    • Note that there is spare space in the last buffer . If there are new elements to be inserted at the end, you can use this spare space directly

Other functions of iterator (with set_node function)

  • The following are several key behaviors of deque iterators. Since various index operations are overloaded in the iterator, various index operations such as addition, subtraction, forward, and backward cannot be visualized.
  • One of the most important key is: once you encounter the edge of the buffer when traveling, you must be especially careful. Depending on forward or backward, you  may need to call set_node() to jump a buffer:
void set_node(map_pointer new_node) {

    node = new_node;

    first = *new_node;

    last = first + difference_type(buffer_size());

}
reference operator*() const { return *cur; }

pointer operator->() const { return &(operator*()); }
difference_type operator-(const self& x) const {
    return difference_type(buffer_size()) * (node - x.node - 1) +(cur - first) + (x.last - x.cur);
}
 
// 参考 More Effective C++, item6: Distinguish between prefix and
// postfix forms of increment and decrement operators.
self& operator++() {
    ++cur;                  //切换至下个元素
    if (cur == last) {   //如果已达所在缓冲区的尾端,就切换至下一节点(亦即缓冲区)的第一个元素
        set_node(node + 1); 
        cur = first;    
    }
    return *this;
}
 
self operator++(int) { //后置式,标准写法
    self tmp = *this;
    ++*this;
    return tmp;
}
 
self& operator--() {
    if (cur == first) {//如果已达所在缓冲区的头端, 就切换至前一节点(亦即缓冲区)的最后一个元素
        set_node(node - 1);
        cur = last;
    }
    --cur; //切换至前一个元素
    return *this;
}
 
self operator--(int) { //后置式,标准写法
    self tmp = *this;
    --*this;
    return tmp;
}
 
// 以下实现随机存取。迭代器可以直接跳跃n个距离
self& operator+=(difference_type n) {
    difference_type offset = n + (cur - first);
    if (offset >= 0 && offset < difference_type(buffer_size()))
        //标的位置在同一缓冲区内
        cur += n;
    else {
        //标的位置不在同一缓冲区内
        difference_type node_offset=
            offset > 0 ? offset / difference_type(buffer_size()): -difference_type((-offset - 1)/buffer_size()) - 1;
 
    // 切换至正确的节点(亦即缓冲区)
    set_node(node + node_offset);
    // 切换至正确的元素
    cur = first + (offset - node_offset * difference_type(buffer_size()));
    }
    return *this;
}

 

Fourth, the data structure of deque (start iterator, finish iterator)

  • In addition to maintaining a pointer to the map as mentioned earlier, deque also maintains two iterators: start and finish:
    • Point to the next position of the first element of the first buffer and the last element of the last buffer respectively (see the figure above)
    • In addition, it also remembers the current map size. Because once the nodes provided by the map are insufficient, a larger block map must be reconfigured
// 见 deque_buf_size()。BufSize 默认值为 0 的唯一理由是为了闪避某些
// 编译程序在处理常数算式(constant expressions)时的臭虫。
// 默认使用alloc为配置器
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
    typedef T value_type;
    typedef value_type* pointer;
    typedef size_t size_type;
 
public: // Iterators
    typedef deque_iterator<T, T&, T*, BufSiz> iterator;
 
protected: // Internal typedefs
    // 元素的指针的指针(pointer of pointer of T)
    typedef pointer* map_pointer;
 
protected: // Data members
    iterator start;  // 表现第一个节点。
    iterator finish; // 表现最后一个节点。
    map_pointer map; // 指向 map,map是块连续空间,
                     // 其每个元素都是个指针,指向一个节点(缓冲区)
    size_type map_size; // map内有多少指标
    ...
};
  • With the above structure, the following functions can be easily completed:
public: // Basic accessors
    iterator begin() { return start; }
    iterator end() { return finish; }
 
    reference operator[](size_type n) {
        return start[difference_type(n)]; //调用__deque_iterator<>::operator[]
    }
 
    reference front() { return *start; } //调用__deque_iterator<>::operator*
    reference back() {
        iterator tmp = finish;
        --tmp;       //调用__deque_iterator<>::operator--
        return *tmp; //调用__deque_iterator<>::operator*
        // 以上三行何不改为:return *(finish-1);
        // 因为__deque_iterator<> 没有为 (finish-1) 定义运算符?!
    }
 
    //下行最后有两个 ‘;’,虽奇怪但合乎语法
    size_type size() const { return finish - start;; }
 
    // 以上唤起 iterator::operatorsize_type
    max_size() const { return size_type(-1); }
    bool empty() const { return finish == start; }

5. The structure of deque is based on memory management (constructor, push_back, push_front)

memory allocation of deque

  • The deque buffer expansion action is quite trivial and complicated. The following will explain step by step in the way of decomposing the action
  • At the beginning of the program, a deque is declared:
deque<int,alloc,8> ideq(20,9);
  • Its buffer size is 8 (save 8 int type elements), and make it reserve 20 element space, and the initial value of each element is 9. In order to specify the third template parameter (buffer size) of deque, we must specify both the first two parameters (C++ grammar rules), so alloc must be explicitly designated as the space configurator
  • Now, the situation of deque is shown in the figure below (the figure does not show that the initial value of each element is 9)

data_allocator、map_allocator

  • deque defines two exclusive space configurators by itself
protected: // Internal typedefs
    //专属之空间配置器,每次配置一个元素大小
    typedef simple_alloc<value_type, Alloc> data_allocator;
    // 专属之空间配置器,每次配置一个指针大小
    typedef simple_alloc<pointer, Alloc> map_allocator;

 

Constructor

  • The version of one of the constructors is as follows
deque(int n, const value_type& value): start(), finish(), map(0), map_size(0)

{

    fill_initialize(n, value);

}

fill_initialize()

  • The fill_initialize() called inside is responsible for generating and arranging the structure of the deque, and setting the initial value of the element properly:
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::fill_initialize(size_type n,const value_type& value) {
 
    create_map_and_nodes(n); //把deque的结构都产生并安排好
    map_pointer cur;
__STL_TRY 
{
    //为每个节点的缓冲区设定初值
    for (cur = start.node; cur < finish.node; ++cur)
        uninitialized_fill(*cur, *cur + buffer_size(), value);
    // 最后㆒个节点的设定稍有不同(因为尾端可能有备用空间,不必设初值)
    uninitialized_fill(finish.first, finish.cur, value);
}
    catch(...) {
        ...
    }
}

create_map_and_nodes()

  • Among them, create_map_and_nodes() is responsible for generating and arranging the structure of the deque:
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements)
{
    //需要节点数=(元素个数/每个缓冲区可容纳的元素个数)+1
    // 如果刚好整除,会多配一个节点
    size_type num_nodes = num_elements/buffer_size() + 1;
 
    //一个 map 要管理几个节点。最少8个,最多是 “所需节点数加 2”
    // (前后各预留一个,扩充时可用)
    map_size = max(initial_map_size(), num_nodes + 2);
    map = map_allocator::allocate(map_size);
    // 以上配置出一个 “具有 map_size个节点” 的 map
 
    // 以下令 nstart和 nfinish指向 map所拥有之全部节点的最中央区段
    // 保持在最中央,可使头尾两端的扩充能量一样大。每个节点可对应一个缓冲区
    map_pointer nstart = map + (map_size - num_nodes) / 2;
    map_pointer nfinish = nstart + num_nodes - 1;
 
    map_pointer cur;
__STL_TRY 
{
    // 为 map内的每个现用节点配置缓冲区。所有缓冲区加起来就是 deque的
    // 可用空间(最后一个缓冲区可能留有一些余裕)
    for (cur = nstart; cur <= nfinish; ++cur)
    *cur = allocate_node();
}
 
    catch(...) {
        // "commit or rollback" 语意:若非全部成功,就一个不留
        ...
    }
 
    // 为 deque内的两个迭代器start和end设定正确内容
    start.set_node(nstart);
    finish.set_node(nfinish);
    start.cur = start.first; // first, cur都是 public
    finish.cur = finish.first + num_elements % buffer_size();
    // 前面说过,如果刚好整除,会多配一个节点
    // 此时即令 cur指向这多配的一个节点(所对映之缓冲区)的起头处
}

Illustrated

  • In the above demonstration case, our third buffer still has 4 spaces to use. Now we execute the following code to add 3 elements at the end:
for(int i=0;i<3;i++)

ideq.push_back(i);
  •  Since there are still 4 spare element spaces in the last buffer at this time, it will not cause the reconfiguration of the buffer. The deque state at this time is shown in the figure below

push_front()

  • The following is the content of the push_back() function:
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);
}

push_back_aux()

  • When there is no remaining space at the end or there is only one element spare space left, push_back() will call push_back_aux(), first configure a whole new buffer, then set the new element content, and then change the state of the iterator finish:
// 只有当 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(); //若符合某种条件则必须重换一个map
    *(finish.node + 1) = allocate_node(); //配置一个新节点(缓冲区)
__STL_TRY {
    construct(finish.cur, t_copy); //针对标的元素设值
    finish.set_node(finish.node + 1); //改变 finish,令其指向新节点
    finish.cur = finish.first; //设定 finish的状态
}
__STL_UNWIND(deallocate_node(*(finish.node + 1)));
}

Demonstration description

  • If the current state of deque is as shown in the figure above. Now, if you add a new element to the end:
ideq.push_back(3);
  • Now, the status of the deque will be as follows, and an additional buffer has been applied

push_front()

  • The prototype of the push_front() function is as follows:
public: // push_* and pop_*
    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_front_aux ()

  • If the first buffer is no spare space, then it will call push_front_aux ()

    • This function calls reserve_map_at_front() from the beginning, the latter is used to determine whether the map needs to be expanded, and to act if necessary. I will show the contents of the reserve_map_at_front() function later

//只有当 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(); // 若符合某种条件则必须重换一个map
    *(start.node - 1) = allocate_node(); // 配置一个新节点(缓冲区)
__STL_TRY {
    start.set_node(start.node - 1); //改变 start,令其指向新节点
    start.cur = start.last - 1; //设定start的状态
    construct(start.cur, t_copy); //针对标的元素设值
}
    catch(...) {
        // "commit or rollback" 语意:若非全部成功,就一个不留
        start.set_node(start.node + 1);
        start.cur = start.first;
        deallocate_node(*(start.node - 1));
        throw;
    }
}

Demo case

  • If the state of deque is as shown in the figure above, we execute the following code at this time:
ideq.push_front(99);
  • The state in the above figure does not need to reorganize the map, so the subsequent process configures a new buffer and directly places the node on the existing map, then sets new elements, and then changes the state of the iterator start, as shown in the following figure Show

  • If we now add two elements to the head, the structure will update as shown below:
ideq.push_front(98);

ideq.push_front(97);

map redistribution

  • The push_back_aux(), push_front_aux() functions introduced above, these functions will first call reserve_map_at_back(), reserve_map_at_front() to re-rectify the map

reserve_map_at_back()

void reserve_map_at_back (size_type nodes_to_add = 1) {
    if (nodes_to_add + 1 > map_size - (finish.node - map))
        // 如果map尾端的节点备用空间不足
        // 符合以上条件则必须重换一个 map(配置更大的,拷贝原来的,释放原来的)
        reallocate_map(nodes_to_add, false);
}

reserve_map_at_front()

void reserve_map_at_front (size_type nodes_to_add = 1) {
    if (nodes_to_add > start.node - map)
        // 如果map前端的节点备用空间不足
        // 符合以上条件则必须重换一个map(配置更大的,拷贝原来的,释放原来的)
        reallocate_map(nodes_to_add, true);
}

reallocate_map()

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;
    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;
        // 配置一块空间,准备给新 map使用。
        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);
        //把原map内容拷贝过来
        copy(start.node, finish.node + 1, new_nstart);
        //释放原map
        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);
}

 

Six, deque element operation

  • pop_back、pop_front、clear、erase、insert

pop_back()

void pop_back() 
{
    if (finish.cur != finish.first) {
        //最后缓冲区有一个(或更多)元素
        --finish.cur; // 调整指针,相当于排除了最后元素
        destroy(finish.cur); // 将最后元素析构
    }
    else
        //最后缓冲区没有任何元素
        pop_back_aux(); // 这里将进行缓冲区的释放工作
}

 pop_back_aux()

// 只有当 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); // 调整 finish 的状态,使指向
    finish.cur = finish.last - 1; //上一个缓冲区的最后一个元素
    destroy(finish.cur); // 将该元素析构
}

pop_front()

void pop_front() 
{
    if (start.cur != start.last - 1) 
    {
        //第一缓冲区有一个(或更多)元素
        destroy(start.cur); //将第一元素析构
        ++start.cur; //调整指针,相当于排除了第一元素
    }
    else
        //第一缓冲区仅有一个元素
        pop_front_aux(); // 这里将进行缓冲区的释放工作
}

pop_front_aux ()

// 只有当 start.cur == start.last - 1时才会被调用
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); // 调整 start 的状态,使指向
    start.cur = start.first;        //下一个缓冲区的第一个元素
}

 

clear()

  • clear() is used to clear the entire deque
  • Please note that the initial state of the deque (when there are no elements) retains a buffer , so after clear() is completed, the initial state is restored, and a buffer is also reserved:
// 注意,最终需要保留一个缓冲区。这是deque的策略,也是deque的初始状态
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() 第二版本
        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; //调整状态
}

 

erase()

  • The following erase version is used to clear an element
// 清除pos所指的元素。pos为清除点
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;
}
  • The following erase version is used to clear all elements in the (first, last) range
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) { // 如果清除区间就是整个deque
        clear();                            // 直接调用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;           // 标记 deque 的新起点
            destroy(start, new_start);                // 移动完毕,将冗余的元素析构
            // 以下将冗余的缓冲区释放
            for (map_pointer cur = start.node; cur < new_start.node; ++cur)
            data_allocator::deallocate(*cur, buffer_size());
            start = new_start; // 设定 deque 的新起点
        }
 
        else { // 如果清除区间后方的元素比较少
            copy(last, finish, first);        // 向前搬移后方元素(覆盖清除区间)
            iterator new_finish = finish - n; // 标记 deque 的新尾点
            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; // 设定 deque 的新尾点
        }
        return start + elems_before;
    }
}

 

insert() function

  • insert provides many versions, the most basic and most important is the following version, which allows to insert an element at a certain point (before) and set its value
// 在 position 处插入一个元素,其值为 x
iterator insert(iterator position, const value_type& x) {
    if (position.cur == start.cur) { // 如果插入点是 deque 最前端
        push_front(x);               //交给push_front去做
        return start;
    }
    else if (position.cur == finish.cur) { //如果插入点是 deque 最尾端
        push_back(x);                      // 交给 push_back 去做
        iterator tmp = finish;
        --tmp;
    }
    else {
        return insert_aux(position, x); // 交给 insert_aux 去做
    }
}

insert_aux() function

template <class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) 
{
    difference_type index = pos - start; // 插入点之前的元素个数
    value_type x_copy = x;
 
    if (index < size() / 2) 
    {                            // 如果插入点之前的元素个数比较少
        push_front(front());     // 在最前端加入与第一元素同值的元素
        iterator front1 = start; // 以下标示记号,然后进行元素移动...
        ++front1;
        iterator front2 = front1;
        ++front2;
        pos = start + index;
        iterator pos1 = pos;
        ++pos1;
        copy(front2, pos1, front1); // 元素移动
    }
    else 
    {                             // 插入点之后的元素个数比较少
        push_back(back());        // 在最尾端加入与最后元素同值的元素
        iterator back1 = finish;  // 以下标示记号,然后进行元素搬移...
        --back1;
        iterator back2 = back1;
        --back2;
        pos = start + index;
        copy_backward(pos, back2, back1); // 元素移动
    }
    *pos = x_copy; // 在插入点上设定新值
    return pos;
}

 

Guess you like

Origin blog.csdn.net/www_dong/article/details/113959278