《STL源码剖析》笔记-deque

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/WizardtoH/article/details/82627286

上一篇:《STL源码剖析》笔记-list

deque是一种双向开口的分段连续线性空间,可以在头部/尾部进行元素的插入和删除。它与vector最大的差异,一是deque允许于常数时间内对头端进行插入或删除元素,二是deque是由分段连续线性空间组合而成,随时可以增加一段新的空间,不像vector那样,vector当内存不够时,需要重新分配空间。
deque也提供Ramdom Access Iterator,但是不是原始指针,相比起来要复杂得多,因此也影响了运算效率,所以尽量使用vector少用deque。

deque的中控器

deque在逻辑上是连续线性空间,由一段一段的定量连续空间构成。一旦需要在前端或尾端增加新空间,就配置一段定量的连续空间进行串接。与vector相比避免了重新配置空间的消耗,代价就是更复杂的迭代器。为了管理这些分段空间,deque引入了所谓的map作为主控。map是一小块连续空间,其中每个元素都是指向缓冲区的指针,缓冲区才是deque存储数据的主体:


// SGI STL运行自定义缓冲区大小,BufSiz为0时默认使用至少512bytes的缓冲区
// (原作者说是512应该不是很准确,见下文__deque_buf_size的解释)
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:
    typedef T value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

protected:
    typedef pointer* map_pointer;  // 元素指针的指针
    map_pointer map;      // 指向map, map是一个连续的空间, 其中的每个元素都是一个指向缓冲区的指针
    size_type map_size;   // map容量
...
}

这里写图片描述

deque的迭代器

deque分段连续空间的实现,主要有迭代器的operator++和operator–来负责。deque迭代器必须能够判断目前的缓冲区,以及是否在当前缓冲区的边界,这样才能决定是否要跳转到另一个缓冲区。而为了找到其他缓冲区,必须要能够控制中控器map。

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_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)); }

    typedef random_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T** map_pointer;

    typedef __deque_iterator self;

    // 保持与容器的联结
    T* cur;           // 缓冲区中的当前元素
    T* first;         // 缓冲区中的头
    T* last;          // 缓冲区中的尾
    map_pointer node; // 指向中控器
...
}

前面说到deque的缓冲区大小是固定的,具体值由__deque_buf_size函数计算出的结果与sizeof(T)相乘得到:

// 前面说到,默认BufSiz为0时,默认使用至少512bytes的缓冲区
// 当模板类型占用空间小于512时,默认缓冲区大小为 size_t(512 / sz) * sz = 512
// 而当模板类型占用空间大于512时,默认缓冲区大小为 size_t(1) * sz > 512
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));
}

这里写图片描述

deque的迭代器对各种指针运算都进行了重载,其中最关键的就是当遇到缓冲区边缘时需要特殊处理:

// 跳转缓冲区
void set_node(map_pointer new_node)
{
      node = new_node;      
      first = *new_node;    // 更新跳转后缓冲区first信息
      last = first + difference_type(buffer_size());  // 更新跳转后缓冲区last的信息
}

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

// 前缀自增
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;
}

// 实现迭代器跳跃功能
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;
}

self operator+(difference_type n) const
{
    self tmp = *this;
    return tmp += n;
}

self& operator-=(difference_type n) { return *this += -n; }

self operator-(difference_type n) const {
    self tmp = *this;
    return tmp -= n;
}

reference operator[](difference_type n) const { return *(*this + n); }

bool operator==(const self& x) const { return cur == x.cur; }
bool operator!=(const self& x) const { return !(*this == x); }
bool operator<(const self& x) const {
    return (node == x.node) ? (cur < x.cur) : (node < x.node);
}

deque的数据结构

deque处理维护指向map的指针外,还需要维护start和finish两个迭代器,分别是第一个缓冲区的第一个元素和最后一个缓冲区的最后一个元素后一个位置。还需要记录当前map的大小,如果空间不足就需要重新配置:

template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:
    typedef T value_type;
    typedef value_type* pointer;
    typedef size_t size_type;
...
public:
    typedef __deque_iterator<T, T&, T*, BufSiz>  iterator;    // deque的迭代器
protected:
    typedef pointer* map_pointer;
protected:
    iterator start;               // 指向第一个的迭代器
    iterator finish;              // 指向最后一个节点的迭代器

    map_pointer map;      // map指针
    size_type map_size;   // map容量 

public:
  iterator begin() { return start; }    // 返回第一个节点的迭代器
  iterator end() { return finish; }     // 返回最后一个节点的迭代器
  const_iterator begin() const { return start; }    // const版本
  const_iterator end() const { return finish; }     // const版本
public:
    iterator begin() { return start; }    // 返回第一个节点的迭代器
    iterator end() { return finish; }     // 返回最后一个节点的迭代器
    const_iterator begin() const { return start; }    // const版本
    const_iterator end() const { return finish; }     // const版本

    // 使用deque迭代器的[]操作符
    reference operator[](size_type n) { return start[difference_type(n)]; }

    reference front() { return *start; }
    reference back() {
        iterator tmp = finish;
        --tmp;
        return *tmp;
    }

    // 返回deque的大小,使用迭代器的-运算符 
    size_type size() const { return finish - start; }
    // 返回deque最大容量
    size_type max_size() const { return size_type(-1); }

    bool empty() const { return finish == start; }
}

deque的构造与内存管理

以下举例deque中的一个构造函数:

template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:
    deque(size_type n, const value_type& value)
        : start(), finish(), map(0), map_size(0)
    {
        fill_initialize(n, value);    // 调用fill_initialize函数
    }

protected:
    // 专属配置器,一个元素大小
    typedef simple_alloc<value_type, Alloc> data_allocator;
    // 专属配置器,一个指针大小
    typedef simple_alloc<pointer, Alloc> map_allocator;

    pointer allocate_node() { return data_allocator::allocate(buffer_size()); }
    void deallocate_node(pointer n) {
        data_allocator::deallocate(n, buffer_size());
    }
}

// 分配n个结点, 并以value为元素值初始化
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);  // 配置map和缓冲区
    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 (...) {
        for (map_pointer n = start.node; n < cur; ++n)
            destroy(*n, *n + buffer_size());
        destroy_map_and_nodes();
        throw;
    }
}

// 配置并安排好
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
    // 计算需要的缓冲区节点数,刚好对缓冲器元素个数整除则多配置一个节点
    size_type num_nodes = num_elements / buffer_size() + 1;

    // map需要管理几个节点, = 所需节点前后+2(最少8个)
    map_size = max(initial_map_size(), num_nodes + 2);
    map = map_allocator::allocate(map_size);

    // nstart和nfinish指向实际需要节点的头和尾
    map_pointer nstart = map + (map_size - num_nodes) / 2;
    map_pointer nfinish = nstart + num_nodes - 1;

    map_pointer cur;
    __STL_TRY{
        // 为所有节点配置缓冲区
        for (cur = nstart; cur <= nfinish; ++cur)
        *cur = allocate_node();
    }
#ifdef  __STL_USE_EXCEPTIONS 
        catch (...) {
        for (map_pointer n = nstart; n < cur; ++n)
            deallocate_node(*n);
        map_allocator::deallocate(map, map_size);
        throw;
    }
#endif /* __STL_USE_EXCEPTIONS */

    // 为deque中的两个迭代器设置内容
    start.set_node(nstart);
    finish.set_node(nfinish);
    start.cur = start.first;
    finish.cur = finish.first + num_elements % buffer_size();
}

例如,定义一个元素个数为20个,且BufSiz为32的deque,然后在尾部添加三个元素。那么一个缓冲区能存放8个int型元素,共需要3个缓冲区,map中有8个节点(因为至少8个)。此时还剩余4个空闲的元素空间,添加三个元素后不需要重新分配。

std::deque<int, alloc, 32> deq(20, 0);

// 重新赋值
for (int i = 0; i < deq.size(); ++i)
{
    deq[i] = i;
}

for(int = 0; i < 3; ++i)
{
    deq.push_back(i);
}

这里写图片描述

push_back/push_front的实现如下:

void push_back(const value_type& t) {
    // 当前缓冲区备用空间需要大于等于2个,否则调用push_back_aux,其中进行了空间配置
    if (finish.cur != finish.last - 1) {
        construct(finish.cur, t);
        ++finish.cur;
    }
    else
        push_back_aux(t);
}

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.cur = finish.first;
    }
    __STL_UNWIND(deallocate_node(*(finish.node + 1)));
}

void push_front(const value_type& t) {
    if (start.cur != start.first) {
        construct(start.cur - 1, t);
        --start.cur;
    }
    else
        push_front_aux(t);
}

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{
        start.set_node( sart.node - 1);
        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_EXEONS */

void reserve_map_at_back(size_type nodes_to_add = 1)
    if (nodes_to_add + 1 > map_size - (finish.node - map))
        // 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)
        // map头部的节点备用空间不足,重新配置
        reallocate_map(nodes_to_add, rue);
}

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add, bool add_at_front) {
    // 计算新map需要的节点数量
    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) {   
        // 当前map节点数量是需要的节点数量2倍以上,说明map中还有剩余备用空节点可用
        // 移动map起点,并拷贝原有节点中的缓冲区地址到新的位置
        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 {
        // 当前map节点数量不到需要的节点数量2倍,那么需要重新配置map节点空间
        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);
        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);
}

deque的元素操作

deque提供的元素操作函数很多,下面只举例常用的几个。

void pop_front() {
    if (start.cur != start.last - 1) { 
        destroy(start.cur);
        ++start.cur;
    }
    else 
        pop_front_aux();
}

// 当前缓冲区只剩一个元素
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.cur = start.first;
}

void pop_back()
{

  if (finish.cur != finish.first)
  {
    destroy(finish.cur);
    --finish.cur;
  }
  else
    pop_back_aux();         
}

// 被删的节点刚好是最后一个缓冲区的第一个节点
template<class T, class Alloc, size_t BufSize>
void deque<T,Alloc,BufSize> ::pop_back_aux()
{
   deallocate_node(finish.first) ;
   destroy(finish.cur) ;
   finish.setNode( finish.node-1 ) ;
   finish.cur = finish.last-1 ;
}

// 删除某个位置的元素
iterator erase(iterator pos) {
    iterator next = pos;
    ++next;
    difference_type index = pos - start;
    if (index < (size() >> 1)) {
        // 如果清除点之前的元素比较少
        // 将清除点之前的所有元素后移一位 ,然后删除第一个元素
        copy_backward(start, pos, next);  // 利用了迭代器的operator--
        pop_front();
    }
    else { 
        // 如果清除点之后的元素比较少
        // 将清除点之后的所有元素前移一位  然后删除最后一个元素
        copy(next, finish, pos);  // 利用了迭代器的operator++
        pop_back();
    }
    return start + index;
}

// 根据插入位置的不同进行处理
iterator insert(iterator position, const value_type& x) {
    if (position.cur == start.cur) {
        push_front(x);
        return start;
    }
    else if (position.cur == finish.cur) {
        push_back(x);
        iterator tmp = finish;
        --tmp;
        return tmp;
    }
    else {
        return insert_aux(position, x);
    }
}

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) {
        // 插入点之前的元素较少
        // 先在前端插入一个元素,然后把开始到pos的元素向前移动一位
        push_front(front());
        iterator front1 = start;
        ++front1;
        iterator front2 = front1;
        ++front2;
        pos = start + index;
        iterator pos1 = pos;
        ++pos1;
        copy(front2, pos1, front1);
    }
    else {
        // 插入点之后的元素较少
        // 先在尾端插入一个元素,然后把pos到尾部的元素向后移动一位
        push_back(back());
        iterator back1 = finish;
        --back1;
        iterator back2 = back1;
        --back2;
        pos = start + index;
        copy_backward(pos, back2, back1);
    }

    // 给pos位置的元素赋值
    *pos = x_copy;
    return pos;
}

下一篇:《STL源码剖析》笔记-stack、queue

猜你喜欢

转载自blog.csdn.net/WizardtoH/article/details/82627286