SGI STL双端队列deque

deque vs vector

vector是单向开口的连续线性空间,适合在尾端插入、删除元素,O(1);deque是双向开口的连续线性空间,适合在头尾两端分别进行元素的插入和删除操作,O(1)。
vector也可以在头尾插入、删除元素,不过在头部操作的效率非常低,O(n)。

可以指定,deque和vector区别在于:
1)deque允许常数时间在头端插入、删除元素,vector需要O(n)时间;
2)deque没有容量(capacity)概念,因为是动态地以分段连续空间组合而成,随时可以增加一段新空间并链接起来。而vector容量不足时,会重新配置一块更大的连续空间,然后将元素复制到新空间并释放旧空间。
3)deque提供Random Access Iterator,但其迭代器并不是普通指针,访问元素效率比vector低。因此,应尽可能选择用vector。对deque的排序,可以先将deque完整复制到一个vector,排序后再复制回deque。


deque中控器

deque是由一段一段的定量连续空间构成。一旦有必要在deque的首端或尾端增加新空间,就会配置一段连续空间,然后串接在deque的首端或尾端。而deque的最大任务就是,在这些分段的连续空间上,维护其整体连续的假象,并提供随机存取的接口。避免vector那种“重新配置、复制、释放”的扩容步骤,代价是复杂的迭代器结构。

为了维护分段空间整体连续的假象,数据结构和迭代器前进、后退等操作繁琐,必须有中央控制。

deque采用一块所谓map作为主控,不是STL的map容器,而是一小块连续空间,可以看作是一个数组,map中每个元素称为节点(即node),指向一个缓冲区,用于存放用户数据。SGI STL允许用户指定缓冲区大小,默认值0表示使用512byte缓冲区。

旧版的SGI STL deque包含对底层空间的维护;而SGI STL v3.3中,deque已经将对底层空间的维护,包括map,封装到了其基类_Deque_base中。

这里为了方便,采用SGI STL v2.03 进行分析。

// deque.h from SGI STL v2.03
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:                         // Basic types
    // 定义内嵌类型
  typedef T value_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
...
protected:                      // Internal typedefs
  typedef pointer* map_pointer;
...
protected:                      // Data members
...
  map_pointer map;
  size_type map_size;
...
};

deque 迭代器

deque是分段连续空间。如何维护“整体连续”的假象?
这个重任落在operator++和operator--身上,而其实现离不开deque迭代器。deque迭代器必须能1)指出分段连续空间(实际存储数据的缓冲区)的地址;2)判断自己是否已经处于其所在缓冲区的边缘,如果是一旦前进或后退,就要跳跃至下一个或上一个缓冲区。而为了能正确跳跃,必须通过中控器(map)。

deque迭代器(__deque_iterator)的数据结构:

// deque.h from SGI STL v2.03

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, 须自定义标准迭代器要求的5个迭代器关联类型
  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; // 指向中控器
...
};

buffer_size()决定缓冲区大小,其内部调用了全局函数__deque_buf_size():

// deque.h from SGI STL v2.03

// Note: this function is simply a kludge to work around several compilers'
//  bugs in handling constant expressions.
// 如果n不为0, 传回n, 表示buffer size 由用户定义
// 如果n为0, 表示buffer size使用默认值, 那么
// 如果sz(元素大小, sizeof(value_type))小于512, 传回512/sz,
// 如果sz不小于512, 传回1
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的中控器,缓冲区,迭代器的相互关系:

假设我们现在有一个deque,让其缓冲区buffer大小为32byte,那么每个缓冲区可容纳32 / sizeof(int) = 8 个元素。经过一些操作后,deque拥有20个元素,那么begin()(即start),end()(即finish)传回的两个迭代器应该如下图所示。

20个元素,共需要20/3=8个缓冲区(向上取整),所以map内需要3个节点,分别指向这3个缓冲区:迭代器start内的cur指针指向第一个缓冲区的第一个元素,迭代器finish内的cur指针指向最后一个缓冲区的最后一个元素。

每个缓冲区只能提供32byte空间,3个缓冲区就是96byte,20个元素(每个占4byte),共计80byte,也就是说最后会剩16byte,也就是4个元素大小。

deque迭代器内对各种指针运算都做了重载,如各种指针运算(加、减、前进、后退等),特别需要注意的是:一旦移动指针遇到缓冲区边界时,就要特别小心,视前进或后退具体情况而定,可能需要调用set_node()跳跃到下一个缓冲区。

// __deque_iterator<> member function

  void set_node(map_pointer new_node) {
    node = new_node;
    first = *new_node;
    last = first + buffer_size();
  }

    // 下面各个重载函数是__deque_iterator<>成功运作的关键

  reference operator*() const { return *cur; }
  pointer operator->() const { return &(operator*()); }

  difference_type operator-(const self& x) const {
    return buffer_size() * (node - x.node - 1) +
      (cur - first) + (x.last - x.cur);
  }
    
    // prefix increment operator
  self& operator++() {          // 前置式
    ++cur;                      // 切换至下一个元素
    if (cur == last) {          // 如果已经到达缓冲区末尾
      set_node(node + 1);       // 切换至下一个node, 即下一个缓冲区
      cur = first;
    }
    return *this;
  }
    // post increment operator
  self operator++(int)  {       // 后置式
    self tmp = *this;
    ++*this;                    // 注意这里调用前置式递增
    return tmp;
  }
    // prefix decrement operator
  self& operator--() {
    if (cur == first) {         // 如果已经达到所在缓冲区头端
      set_node(node - 1);       // 就切换至前一个node, 即前一个缓冲区
      cur = last;               // 切换至缓冲区最后一个元素(前一个缓冲区的最后一个元素)
    }
    --cur;                      // 切换至前一个元素
    return *this;
  }
    // post decrement operator
  self operator--(int) {
    self tmp = *this;
    --*this;                    // 注意这里调用前置式递减
    return tmp;
  }

    // 以下实现随机存取. 迭代器可以直接跳跃n个距离
    // 前进n个距离
  self& operator+=(difference_type n) { // 形如 x += n
    difference_type offset = n + (cur - first);
    if (offset >= 0 && offset < buffer_size())
      cur += n;
    else {
      difference_type node_offset =
        offset > 0 ? offset / buffer_size()
                   : -difference_type((-offset - 1) / buffer_size()) - 1;
      set_node(node + node_offset);
      cur = first + (offset - node_offset * buffer_size());
    }
    return *this;
  }
    
  self operator+(difference_type n) const { // 形如 x = x + n
    self tmp = *this;
    return tmp += n;
  }
    // 后退n个距离
  self& operator-=(difference_type n) { return *this += -n; } // 形如 x -= n
  self operator-(difference_type n) const { // 形如 x = x - n
    self tmp = *this;
    return tmp -= n;
  }
    
    // 随机存取, 迭代器直接跳跃n个距离, 调用了operator+
  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的指针(map),start、finish 两个迭代器:指向第一个缓冲区、最后一个缓冲区。维护当前map大小(map_size)。当map所能容纳的节点数不足时,就需要扩容,为其配置一块更大map,相当于vector扩容。

// See __deque_buf_size().  The only reason that the default value is 0
//  is as a workaround for bugs in the way that some compilers handle
//  constant expressions.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:                         // Basic types
    // 定义内嵌类型
  typedef T value_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

public:                         // Iterators
  typedef __deque_iterator<T, T&, T*, BufSiz>              iterator;
  typedef __deque_iterator<T, const T&, const T&, BufSiz>  const_iterator;
...
protected:                      // Internal typedefs
  typedef pointer* map_pointer;

protected:                      // Data members
  iterator start;     // map中第一个node
  iterator finish;    // map中最后一个node

  map_pointer map;    // 指向map, map可以看做是一个数组, 是一块连续空间, 每个元素都是个指针, 指向一个node
  size_type map_size; // map内有多少个指针, 即对应有多少个缓冲区
...
};

有了上面的结构,再实现下面获取头尾迭代器,元素个数等信息,就很容易了

public:                         // Basic accessors
  iterator begin() { return start; }
  iterator end() { return finish; }
  const_iterator begin() const { return start; }
  const_iterator end() const { return finish; }
    
    // 反向迭代器
  reverse_iterator rbegin() { return reverse_iterator(finish); }
  reverse_iterator rend() { return reverse_iterator(start); }
  const_reverse_iterator rbegin() const {
    return const_reverse_iterator(finish);
  }
  const_reverse_iterator rend() const {
    return const_reverse_iterator(start);
  }

  reference operator[](size_type n) { return start[n]; } // 调用__deque_iterator<>::operator[]
  const_reference operator[](size_type n) const { return start[n]; }

  reference front() { return *start; } // 调用__deque_iterator<>::operator*
  reference back() {
    iterator tmp = finish;
    --tmp;       // 调用__deque_iterator<>::operator--
    return *tmp; // 调用__deque_iterator<>::operator*
  }
  const_reference front() const { return *start; }
  const_reference back() const {
    const_iterator tmp = finish;
    --tmp;
    return *tmp;
  }
    
    // 下面最后有2个';', 奇观但合语法
    // 求deque的元素个数
  size_type size() const { return finish - start;; } // 调用__deque_iterator<>::operator-
    // 求deque理论上最大元素个数0xFFFFFFFF (UINT32_MAX)
  size_type max_size() const { return size_type(-1); }
    // 判断deque是否为空, 即元素个数是否为0
  bool empty() const { return finish == start; }

deque的构造与内存管理

构造deque及插入元素示例

站在客户角度使用deque,观察deque构造方式、大小变化(size()),插入数据方式(push_front, push_back)。

// 客户角度测试deque

int main()
{
    deque<int, alloc, 32> ideq(20, 9); // 构造deque, 20个值为9的元素, 缓冲区大小32byte
    cout << "size = " << ideq.size() << endl; // 20
    
    // 每个元素设新值
    for (int i = 0; i < ideq.size(); ++i) {
        ideq[i] = i;
    }

    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19
    }
    cout << endl;

    // 在尾端增加3个元素, 值0,1,2
    for (int i = 0; i < 3; ++i) {
        ideq.push_back(i);
    }

    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19 0 1 2
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=23

    // 尾端添加1个元素, 值3
    ideq.push_back(3);
    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19 0 1 2 3
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=24

    // 在最前端增加1个元素, 值99
    ideq.push_front(99);
    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' ';  // 值分别为99 0 1 2 3 ... 19 0 1 2 3
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=25

    // 在最前端增加2个元素, 值分别为98 97
    ideq.push_front(98);
    ideq.push_front(97);
    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为97 98 99 0 1 2 3 ... 19 0 1 2 3
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=27

    // 查找值为99的元素并打印
    deque<int, alloc, 32>::iterator it;
    it = std::find(ideq.begin(), ideq.end(), 99);
    cout << *it << endl;         // 99
    cout << *(it._M_cur) << endl; // 99
    return 0;
}

deque缓冲区是如何扩充的?

分步讲解。
1)用户程序声明一个deque。
缓冲区大小32byte(第三个模板参数),保留20个元素空间,每个元素初值9。

deque<int, alloc, 32> ideq(20, 9);

2)deque自定义空间配置器。

protected:                      // Internal typedefs
    // deque内嵌的专属空间配置器, 每次配置一个元素大小
  typedef simple_alloc<value_type, Alloc> data_allocator;
    // deque内嵌的专属空间配置器, 每次配置一个指针大小
  typedef simple_alloc<pointer, Alloc> map_allocator;

3)调用构造函数constructor,构造dque

4)调用push_back、push_front,插入数据

下面就构造函数、push_back、push_front等方面进行分析。

constructor

并提供一个constructor:

    // 构造函数
  deque(int n, const value_type& value)
    : start(), finish(), map(0), map_size(0) {
      fill_initialize(n, value);
  }

其内调用的fill_initialize(),负责产生并安排好deque的结构,并设置元素初值

// 负责产生并安排好deque的结构, 并设置元素初值
// @tparam T 元素类型
// @tparam Alloc 内存分配子
// @tparam BufSiz 缓冲区大小
// @param n 元素个数
// @param 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); // 把deque的结构都产生并安排好
  map_pointer cur;
  try {
    // 为每个节点的缓冲区设置初值
    for (cur = start.node; cur < finish.node; ++cur)
      uninitialized_fill(*cur, *cur + buffer_size(), value);
    // 最后一个节点的设定稍有不同, 因为尾端可能有备用空间不需要设初值
    uninitialized_fill(finish.first, finish.cur, value);
  }
  catch(...) {
      // 发生异常时rollback
    for (map_pointer n = start.node; n < cur; ++n)
      destroy(*n, *n + buffer_size());
    destroy_map_and_nodes();
    throw;
  }
}

内部调用的create_map_and_nodes(),负责产生并安排好deque的结构。注意和fill_initialize区别,前者并不负责初值填充。

// 负责产生并安排好deque的结构
// @param num_elements 元素个数
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_size个节点的map
  map = map_allocator::allocate(map_size);
    
    // 让nstart指向map管理node的第一个(如果开头有额外分配的+1节点, 就忽略)
    // 让nfinish指向map管理node的最后一个(如果末尾有额外分配的+1节点, 就忽略)
    // 总之, [nstart, nfinish] 节点数为num_nodes (而非num_nodes+2)
  map_pointer nstart = map + (map_size - num_nodes) / 2;
  map_pointer nfinish = nstart + num_nodes - 1;
    
  map_pointer cur;
  try {
    // 为map内每个节点配置缓冲区. 所有缓冲区加起来就是deque的可用空间,
    // 最后一个缓冲区可能有剩余空间
    for (cur = nstart; cur <= nfinish; ++cur)
      *cur = allocate_node();
  }
  catch(...) {
    // commit or rollback
    for (map_pointer n = nstart; n < cur; ++n)
      deallocate_node(*n);
    map_allocator::deallocate(map, map_size);
    throw;
  }

    // 为deque内两个迭代器start和end设置正确内容
    // 注意这里的finish跟普通迭代器有区别, finish指向的node是有缓冲区, 是有意义的;
    // 普通迭代器的最后一个(end), 通常是没有对应数据的, 只是一个标记
  start.set_node(nstart);
  finish.set_node(nfinish);
  start.cur = start.first;
  finish.cur = finish.first + num_elements % buffer_size();
}

// map初始大小, 管理的节点数量
static size_type initial_map_size() { return 8; }

// 每个缓冲区可容纳元素个数
static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }

以示例中一段为每个元素重新设置,并在末尾插入3个新元素为例:

// 摘自前面示例程序
for (int i = 0; i < ideq.size(); ++i)
    ideq[i] = i;

for (int i = 0; i < 3; ++i
    ideq.push_back(i);

运行完这个代码片段,此时,最后一个缓冲区仍有4个备用元素空间,所以不会引起缓冲区的再配置。此时,deque状态如下图:

push_back

push_back在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 // 最后缓冲区已无(或只剩1个)元素备用空间
      push_back_aux(t);
  }

// Called only if finish.cur == finish.last - 1.
// 最后一个缓冲区备用空间不足(为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, 为map扩容
  *(finish.node + 1) = allocate_node(); // 配置一个新节点(缓冲区)
  try {
    construct(finish.cur, t_copy);      // 调用构造函数, 针对插入元素设值
    finish.set_node(finish.node + 1);   // 改变finish, 令其指向新节点
    finish.cur = finish.first;          // 设定finish的状态
  }
  catch(...) {
      // commit or rollback
    deallocate_node(*(finish.node + 1));
    throw;
  }
}

其中,push_back_aux只有在最后一个缓冲区(迭代器finish所指buffer)的备用空间只剩一个元素剩余时,才会被调用。思路是先配置一整块新的缓冲区,然后再设置新元素的内容,再更新迭代器finish。

也就是说,在上面例子基础上,当备用空间只剩1个元素大小空间时,继续调用push_back()插入新元素3,会导致配置信缓冲区,更新相应的数据结构,备用空间也由原来的1变成新缓冲区的8。deque结构状态变化,如下图所示:

push_front

push_front在deque的头端插入一个新元素。

    // 向头端插入一个元素
  void push_front(const value_type& t) {
    if (start.cur != start.first) {
        // 第一个缓冲区尚有一个以上备用空间
      construct(start.cur - 1, t); // 直接在备用空间上构造元素
      --start.cur;                 // 调整第一个缓冲区的使用状态
    }
    else // 第一个缓冲区无备用空间
      push_front_aux(t);
  }

// 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();       // 如果符合某种条件则必须换一个map, 为map扩容
  *(start.node - 1) = allocate_node(); // 配置一个新节点(即缓冲区)
  try {
    start.set_node(start.node - 1); // 跳转到第一个缓冲区的前一个缓冲区(新配置的缓冲区), 新缓冲区成为新的第一个缓冲区
    start.cur = start.last - 1;     // 更新第一个缓冲区的待插入数据位置(start.cur)为第一个缓冲区末尾
    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;
  }
}

在push_back向尾端插入3的例子基础上,接着利用push_front向头端插入99,deque的结构状态变成如下图所示。由于第一个缓冲区前面已无备用空间,故需要调用push_front_aux()申请新的空间,第一个缓冲区备用空间也由原来的0增加为新值7。同时,也需要更新相应的迭代器状态。

如果借着调用push_front,向deque头部插入98,97,那么就可以直接在备用空间上构造新元素,而无需申请新的缓冲区,因为第一个缓冲区备用空间足够(并非为空)。deque的结构状态会变成:

push_back 和push_front中,分别调用了一个很重要的函数reserve_map_at_back(),reserve_map_at_front()。该函数用来在必要的时候,重新配置map,为其扩容。

  // Makes sure the map has space for new nodes.  Does not actually
  //  add the nodes.  Can invalidate map pointers.  (And consequently,
  //  deque iterators.)
    
    // 如果map尾端的节点备用空间不足, 就换一个更大的map
  void reserve_map_at_back (size_type nodes_to_add = 1) {
      // 判断map尾端节点备用空间是否足够容纳nodes_to_add个node
    if (nodes_to_add + 1 > map_size - (finish.node - map))
      reallocate_map(nodes_to_add, false); // 重新配置一个map(配置更大的, 拷贝原来的到新的, 释放原来的)
  }
  
    // 如果map前端的节点备用空间不足, 就换一个更大的map
  void reserve_map_at_front (size_type nodes_to_add = 1) {
      // 判断map头端节点备用空间是否足够容纳nodes_to_add个node
    if (nodes_to_add > start.node - map)
      reallocate_map(nodes_to_add, true); // 重新配置一个map(配置更大的, 拷贝原来的到新的, 释放原来的)
  }


// 满足一定条件时, 为map重新配置新空间, 相当于vector扩容
// 主要工作: 配置更大空间, 拷贝原来元素到新空间, 释放原来空间
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; // 已经在使用的node数
  size_type new_num_nodes = old_num_nodes + nodes_to_add; // 旧的node数 + 待添加的node数

  map_pointer new_nstart;
  if (map_size > 2 * new_num_nodes) {
      // map空间足够: map现有大小超过2倍的新node数
    new_nstart = map + (map_size - new_num_nodes) / 2
                     + (add_at_front ? nodes_to_add : 0); // 计算新的第一个缓冲区对应node在map中位置
    // 如果原来node起点更早, 就从左到右将内容拷贝到新区间
    if (new_nstart < start.node)
      copy(start.node, finish.node + 1, new_nstart);
    // 如果原来的node起点更晚, 就从右到左将内容拷贝到新区间
    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);
}

deque的元素操作

关于元素操作,只讲几个典型的member function。

pop_back

pop_back 从末尾弹出一个元素。可能会引起最后一个缓冲区释放。


  // 从末尾弹出一个元素, 缓冲区为空时, 将缓冲区释放掉
  void pop_back() {
    if (finish.cur != finish.first) {
        // 最后一个缓冲区有一个(或更多)元素
      --finish.cur;        // 调整指针, 相当于排除了最后元素
      destroy(finish.cur); // 析构最后元素
    }
    else
        // 最后一个缓冲区没有任何元素, 将缓冲区释放掉
      pop_back_aux();
  }

// Called only if finish.cur == finish.first.
// 只有当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

pop_front 从头端弹出一个元素。可能会引起第一个缓冲区释放。

  // 从头端弹出一个元素, 缓冲区为空时, 将缓冲区释放掉
  void pop_front() {
    if (start.cur != start.last - 1) {
        // 第一个缓冲区有1个以上元素
      destroy(start.cur);  // 将第一个元素析构
      ++start.cur;         // 调整指针, 相当于排除了第一元素
    }
    else // 第一个缓冲区只有1个元素, 释放缓冲区
      pop_front_aux();
  }

// 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.
// 只有当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;
}

erase

清除指定位置元素。清除元素后,会导致其他元素的移动,具体是前移,还是后移,取决于前后哪个部分的元素较少。

public:                         // Erase
    // 清除pos所指元素
  void erase(iterator pos) {
    iterator next = pos;
    ++next;
    
    // 如果清除点之前元素比较少( < 现有元素数量一半), 就移动清除点以前的元素
    if (pos - start < size() / 2) {
      copy_backward(start, pos, next); // 从后往前移动元素
      pop_front(); // 移动完毕, 最前一个元素冗余, 去除之
    }
    else { // 清除点之后的元素比较少
      copy(next, finish, pos); // 移动清除点之后的元素
      pop_back();              // 移动完毕, 最后一个元素冗余, 去除之
    }
  }

清除指定区间[first, last)内所有元素。

// 清除[first, last)范围内所有元素
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::erase(iterator first, iterator last) {
  if (first == start && last == finish) // 如果清除区间是整个deque, 直接调用clear()
    clear();
  else {
    difference_type n = last - first;             // 清除区间长度
    difference_type elems_before = first - start; // 清除区间前方元素个数
    if (elems_before < (size() - n) / 2) {        // 如果前方的元素较少, 向后移动前方元素(覆盖清除区间)
      copy_backward(start, first, last);          // 向后移动元素, [start, first) => [xxx, last)
      iterator new_start = start + n;             // 标记deque的新起点, new_start = last-(first-start) = start + n
      destroy(start, new_start);                  // 移动完毕, 将冗余的元素析构
       // 以下将冗余的缓冲区[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);                   // 向前移动元素, [last, finish) => [first, xxx)
      iterator new_finish = finish - n;            // 标记deque的新尾点
      destroy(new_finish, finish);                 // 移动完毕, 将冗余的元素析构, 要销毁的元素个数n = finish - new_finish
      // 以下将冗余的缓冲区[new_finish+1, finish]释放
      for (map_pointer cur = new_finish.node + 1; cur <= finish.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      finish = new_finish; // 设定deque新起点
    }
  }
}

insert

insert也有很多个版本,不过最基础的是下面这个版本,允许在某个点之前插入一个元素, 并设定其值。

public:                         // Insert
    // 在position出插入一个元素x, 值为x
  iterator insert(iterator position, const value_type& x) {
    if (position.cur == start.cur) {       // 如果插入点是deque最前端, 就交给push_front
      push_front(x);
      return start;
    }
    else if (position.cur == finish.cur) { // 如果插入点是deque最尾端, 就交给push_back
      push_back(x);
      iterator tmp = finish;
      --tmp;
      return tmp;
    }
    else {
      return insert_aux(position, x); // 如果插入点是中间位置, 就交给insert_aux
    }
  }

// 在position处插入一个元素, 其值为x
template <class T, class Alloc, size_t BufSize>
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) {            // 如果插入点之前的元素个数比较少, 不到总元素个数1/2
    push_front(front());               // 在最前端加入与第一个元素同值的元素
    iterator front1 = start;           // 下面标示记号, 然后进行元素移动
    ++front1;
    iterator front2 = front1;
    ++front2;
    pos = start + index;
    iterator pos1 = pos;
    ++pos1;
    copy(front2, pos1, front1);        // 往前移动一个单位, [front2, pos1) => [front1, xxx)
  }
  else { // 如果插入点之前的元素个数比较多, 那就把元素整体往后移动
    push_back(back()); // 在尾端插入一个与最后一个元素同值的元素
    iterator back1 = finish;
    --back1;
    iterator back2 = back1;
    --back2;
    pos = start + index;
    copy_backward(pos, back2, back1);  // 往后移动一个单位, [pos, back2) => [xxx, back1)
  }
  *pos = x_copy;                       // 将x赋值给待插入点pos
  return pos;
}

小结

1)deque是双端队列,依赖于中控器+缓冲区来存取数据,中控器管理缓冲区,缓冲区用于存储数据。从而实现在头尾两端O(1)时间复杂度内插入数据,有效避免了vector在头部插入数据时需要整体移动数据的问题。

2)deque的iterator并非继承自标准的迭代器(iterator<>),内含自定义的cur, fisrt, last, node等域,使用时需要特别小心。新版STL可能已经修改,如C++11 MSVC STL中deque迭代器是继承自标准的迭代器。

3)从deque擦除数据(erase)效率并不高,擦除节点在中间位置时,时间复杂度O(n)。

4)如果可以,优先使用vector。因为vector支持O(1)时间随机访问内部元素,内部数据结构维护简单。

posted @ 2022-05-13 10:20  明明1109  阅读(211)  评论(0编辑  收藏  举报