STL源码解析之deque

std::deque(双端队列,double-ended queue)是 C++ 标准库中的一个序列容器,支持在头部和尾部均以 O(1) 时间复杂度插入和删除元素,同时支持随机访问(通过下标 [])。它弥补了 vector 头部操作低效和 list 无法随机访问的不足。

deque的精妙之处在于,它通过一个"分段连续"的数据结构,巧妙地模拟了"整体连续"的假象,从而在O(1)时间内高效地在两端进行数据存取。

核心架构:中控器 + 缓冲区

std::deque 并非单一连续内存,而是由一段段连续缓冲区组成,并由一个核心中央控制器------中控器(通常也称为 "map")来统一管理。当在两端添加新元素时,若当前缓冲区已满,deque会动态分配新的缓冲区,并将其地址记录到中控器中,

中控器(_M_map):一个指针数组,每个指针指向一个缓冲区(块)

┌─────────────────────────────────────────────────────┐

ptr0 → 缓冲区0 │ ptr1 → 缓冲区1 │ ptr2 → 缓冲区2 │ ...

└─────────────────────────────────────────────────────┘

缓冲区0(固定大小连续内存):

┌────┬────┬────┬────┬────┐

│ e0 │ e1 │ e2 │ │ │

└────┴────┴────┴────┴────┘

缓冲区1:

┌────┬────┬────┬────┬────┐

│ e3 │ e4 │ e5 │ e6 │ │

└────┴────┴────┴────┴────┘

cpp 复制代码
protected:                      // Internal typedefs
  // 元素的指標的指標(pointer of pointer of T)
  typedef pointer* map_pointer;	
  typedef simple_alloc<value_type, Alloc> data_allocator;
  typedef simple_alloc<pointer, Alloc> map_allocator;

  static size_type buffer_size() {
    return __deque_buf_size(BufSiz, sizeof(value_type));
  }
  static size_type initial_map_size() { return 8; }

protected:                      // Data members
  iterator start;		
  iterator finish;	
  map_pointer map;	                        
  size_type map_size;	

迭代器 start 指向第一个元素(e0)

迭代器 finish 指向最后一个元素之后(例如 e6 后面的位置)

其核心的数据结构主要由以下几个关键指针组成:

  • map pointer:二级指针,它指向一个动态数组中控器

  • map size中控器当前的最大容量,即它最多能存储多少个指向缓冲区的指针。

  • startfinish:这两个iterator迭代器,分别标志了deque的头部和尾部,它们各自都包含了指向当前缓冲区的信息。

迭代器的精妙设计

deque的随机访问迭代器是实现其"连续性"假象的关键。它的实现相当复杂,核心在于能够处理不同缓冲区之间的跳转。

实现上述逻辑的关键是缓冲区大小 参数__deque_buf_size。在GCC的实现中,deque会选用512 / sizeof(T)1之间的最大值作为缓冲区的容量,除非元素类型T较大。

cpp 复制代码
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));
}

实例:

cpp 复制代码
#include <iostream>
#include <deque>

int main() {
    std::deque<int> dq;

    dq.push_back(10);
    dq.push_back(20);
    dq.push_back(30);
    // 当前: [10, 20, 30]

    dq.push_front(5);
    dq.push_front(1);
    // 当前: [1, 5, 10, 20, 30]

    std::cout << "Third element: " << dq[2] << std::endl;  // 10
    std::cout << "Front: " << dq.front() << ", Back: " << dq.back() << std::endl;

    std::cout << "All elements: ";
    for (int x : dq) std::cout << x << ' ';
    std::cout << std::endl;

    dq.pop_front();  // 移除 1
    // 当前: [5, 10, 20, 30]

    dq.pop_back();   // 移除 30
    // 当前: [5, 10, 20]

    auto it = dq.begin() + 1;  // 指向 10 的位置
    dq.insert(it, 99);         // 在 10 之前插入 99
    // 当前: [5, 99, 10, 20]


    it = dq.begin() + 2;       // 指向 10
    dq.erase(it);              // 删除 10
    // 当前: [5, 99, 20]

    dq.clear();
    std::cout << "Size after clear: " << dq.size() << std::endl;

    std::deque<int> dq2(5, 100);   // 5个100
    std::deque<int> dq3 = {1,2,3,4,5};
    std::deque<int> dq4(dq3.begin(), dq3.end());

    return 0;
}

//运行结果
Third element: 10
Front: 1, Back: 30
All elements: 1 5 10 20 30 
Size after clear: 0

与vector对比

两者都是 C++ 标准库中的序列容器,都支持随机访问,但在底层实现和性能特点上有显著区别。

deque vs vector

特性 std::deque std::vector
头部插入/删除 O(1) -- 高效 O(n) -- 需移动所有元素
尾部插入/删除 O(1) O(1) 均摊(可能扩容)
中间插入/删除 O(n)(但移动元素较少) O(n)(移动后续全部)
随机访问 [] O(1)(常数稍大,需两次解引用) O(1)(指针直接计算)
内存布局 分段连续(多块内存) 单块连续内存
内存预留 reserve 功能 reserve() 预分配
重新分配时迭代器失效 push_front/back 不使已有迭代器失效 扩容时所有迭代器失效
内存开销 稍高(需中控器指针数组) 极低(仅元素本身)
适用场景 需要在两端频繁操作 + 随机访问 尾部操作为主 + 随机访问 + 内存连续性要求高

选择建议

  • 优先用 vector:大多数情况,尤其是尾部插入为主、需要连续内存(如传给 C 接口)、对缓存性能要求极高时。

  • 考虑用 deque:当确实需要在头部也进行大量插入/删除,并且仍需要随机访问时。例如双端队列、滑动窗口、某些调度器。

如果不需要随机访问,只做 FIFO 队列,应直接用 std::queue(默认底层就是 deque)。

相关推荐
LDR0062 小时前
宠物电器供电革新:USB-C PD标准化,重塑30-65W设备体验
c语言·开发语言·宠物
张忠琳2 小时前
【Go 1.26.4】Golang Interface 接口深度解析
开发语言·golang
伊灵eLing2 小时前
GoLang 语言高级(1)
开发语言·后端·golang
zzz_23682 小时前
【Java基础】泛型的门道:伪泛型的真相
java·开发语言
小鱼仙官2 小时前
Windows Qt调用Vs库实现UDP双口接收数据
开发语言·qt
iiiiyu2 小时前
IO流相关编程题
java·大数据·开发语言·数据结构·数据库·mysql
张忠琳2 小时前
【Go 1.26.4】(Part 8) Go 1.26.4 超深度分析 — context + reflect + errors
开发语言·golang
这个DBA有点耶2 小时前
核心系统的高可用与容灾架构:从主从到两地三中心全面解析
java·开发语言·数据库·sql·mysql·架构·运维开发
张忠琳2 小时前
【Go 1.26.4】(Part 3) Go 1.26.4 超深度分析 — Runtime GC 垃圾收集 (mgc*.go + mbitmap.go)
开发语言·golang