std::flat_multimap是C++23基于并行vector实现的有序重复键映射容器,适用于小数据量、读多写少、缓存敏感场景;不适用于高并发、频繁增删、大数据量或需迭代器稳定的场景。

std::flat_multimap 不是高并发友好容器,它根本没做线程安全设计,所有修改操作都会使全部迭代器失效,且不支持并发读写。
std::flat_multimap 是什么、和 std::multimap 有什么本质区别
它不是红黑树,而是两个并行 std::vector:一个存有序 key,一个存对应 value,允许重复 key。插入/删除时需移动后续所有元素,insert() 和 erase() 平均时间复杂度为 O(n),查找用 std::lower_bound + std::upper_bound,仍是 O(log n),但常数更小。
关键差异点:
-
std::multimap迭代器稳定(插入不使已有迭代器失效),std::flat_multimap任意insert/erase后所有迭代器立即失效 -
std::multimap::equal_range(key)返回一对双向迭代器,std::flat_multimap::equal_range(key)返回一对随机访问迭代器,但底层是计算偏移,不是真实指针 -
std::flat_multimap不提供operator[],也不支持at()—— 因为 key 不唯一,无法定义“单个值语义” - 内存布局连续,遍历时 cache miss 极少;
std::multimap每个节点单独分配,指针跳转多,cache 不友好
什么时候该用 std::flat_multimap,什么时候坚决不用
适用场景(必须同时满足):
立即学习“C++免费学习笔记(深入)”;
- 数据量小(
- 写操作极少(如初始化后只读),或写操作集中在末尾(
push_back等效行为) - 热点路径是批量遍历(如日志聚合、分组渲染)或按 key 范围查(
equal_range),而非单 key 随机查 - 能接受每次
insert后手动重取迭代器,或改用范围操作(如insert(v.begin(), v.end()))
必须避开的场景:
- 需要在循环中边遍历边
insert或erase—— 会触发未定义行为(UB) - 元素数 > 5k,此时移动成本压倒二分优势,性能反不如
std::multimap - 要求异常安全强保证 ——
insert可能因移动value抛异常,且已插入部分无法回滚 - 需要稳定引用或指针(如缓存 value 地址)——
std::flat_multimap重排时所有地址失效
常见错误现象与安全写法对比
典型崩溃代码:
std::flat_multimap<int, std::string> mm{{1,"a"}, {1,"b"}, {2,"c"}};
for (auto it = mm.begin(); it != mm.end(); ++it) {
if (it->first == 1) mm.erase(it); // ❌ UB:erase 后 it 失效,++it 访问野地址
}
安全替代方案:
- 用
erase(key)批量删:mm.erase(1); - 用
equal_range+ 索引删(避免迭代器):auto [f, l] = mm.equal_range(1); mm.erase(f, l); - 收集待删 key 后统一处理:
std::vector<int> to_erase; for (auto& p : mm) if (p.first == 1) to_erase.push_back(p.first); for (int k : to_erase) mm.erase(k);</int> - 初始化阶段用范围构造,运行期尽量只读:
std::flat_multimap<int> mm(v.begin(), v.end());</int>
为什么它不适用于高并发场景
它没有任何内部锁,也没有无锁设计:
- 所有非常量成员函数(
insert、erase、clear)都可能重分配底层vector,导致所有线程看到的迭代器/指针全变野指针 - 即使只读线程也在访问同一块内存,而
insert可能触发vector::resize,引发写时复制竞争(尤其在共享内存或 mmap 场景) - 标准未规定其并发语义,
const成员函数(如find、equal_range)仅保证不修改对象,但不保证与其他线程的修改操作同步 - 若真需并发读写,应外包互斥体(如
std::shared_mutex),但此时性能优势基本归零,不如直接用std::multimap+std::shared_mutex
真正高并发友好的替代方案是 absl::flat_hash_multimap(带分片锁)或自研 lock-free skip list,而不是依赖 std::flat_multimap。


















