std::deque内存由非连续chunk组成,通过中控数组管理,随机访问需两次寻址;头尾插入删除均摊o(1),中间插入删除为o(n);迭代器仅在对应chunk销毁时失效;适用场景为需稳定头尾操作+较快速随机访问。

std::deque 的内存布局不是连续数组
它用一段段固定大小的缓冲区(通常叫 chunk 或 buffer)组成,这些缓冲区地址不连续,由一个中控数组(map)管理——这个 map 本身是 std::vector 或类似结构,存的是各 chunk 的首地址指针。
所以 operator[] 随机访问要两步:先算出下标落在第几个 chunk,再算 chunk 内偏移。常数时间但比 std::vector 多一次指针解引用。
- chunk 大小通常是实现定义的(GCC libstdc++ 是 512 字节,MSVC 是 4096 字节),和元素类型无关,只跟对齐与分配策略有关
- 中控数组会动态增长,但扩容代价远小于 vector 的整体搬移——它只新增指针,不复制元素
- 别指望
&dq[0]得到“整个 deque 的首地址”;它只是第一个 chunk 的首地址,且dq.data()在 C++11 后才存在,还只对非空 deque 有效
头尾插入为什么快,中间插入为什么慢
头尾操作直接在当前首/尾 chunk 的边界进行,只要 chunk 没满,就是 O(1);满了就分配新 chunk 并更新中控数组,均摊仍是 O(1)。
但 insert(dq.begin() + i, x) 这种中间插入,必须把 i 位置之后的所有元素逐个后挪——不是 memcpy,而是调用元素的移动/拷贝构造函数,复杂度是 O(n),和 vector 一样糟。
立即学习“C++免费学习笔记(深入)”;
-
push_front()/push_back()安全,pop_front()/pop_back()也安全,它们不触发元素搬移 -
insert()和erase()只建议用于首尾附近(比如前 3 个或后 3 个位置),否则性能断崖式下跌 - 如果真需要频繁中间修改,
std::list或std::vector配合std::rotate可能更合适,得看具体访问模式
迭代器失效规则和 vector 完全不同
std::deque 迭代器只在对应 chunk 被销毁时才失效——比如 pop_front() 把整个首 chunk 清空了,那所有指向该 chunk 的迭代器立刻变悬垂指针。
但插入、删除其他位置的元素,不会让已有迭代器失效(除非碰巧触发中控数组 realloc,但标准保证这不会导致已有 chunk 被释放)。
-
push_back()不会让begin()失效;push_front()也不会让end()失效 -
erase(it)只让it本身失效,前后迭代器依然可用 - 唯一要注意的是:用
it + n算出来的迭代器,如果中间有 chunk 被 pop 掉,可能跳进未初始化内存——别依赖“迭代器算术”跨 chunk 做大量偏移
什么时候该选 deque 而不是 vector 或 list
核心场景就一个:需要稳定 O(1) 的头尾增删,同时又要求 reasonably 快的随机访问(比如实现滑动窗口、双端队列缓存、BFS 层序遍历)。
别因为“deque 支持 push_front”就默认它比 vector 更通用——实际中 cache 友好性差很多,尤其在小对象高频访问时,vector 的连续性优势碾压 deque 的两层间接寻址。
- 如果你从不
push_front(),只用push_back()和operator[],那std::vector几乎总是更好 - 如果你要频繁
erase()中间元素,且迭代器不能失效,std::list或std::forward_list更合适(虽然失去随机访问) - 注意调试器里看 deque 内容往往很费劲——IDE 通常只显示第一个 chunk,得手动查中控数组,这点比 vector 直观差得多
底层结构决定了它没法两头兼顾到极致:随机访问带间接开销,插入又没 list 那么“局部”,真正平衡点其实很窄,用之前最好跑个 micro-benchmark 对比 vector。











