导读:本期聚焦于董浩然创作的《C++23的std::flat_multiset有什么优势?有序数组容器深度解析》,敬请观看详情。为什么C++23要引入std::flat_multiset这个新容器?它和传统的std::multiset到底该怎么选?flat_multiset底层采用连续存储的有序数组实现,虽然插入删除是O(n)复杂度,但查找性能可以媲美std::vector上的二分搜索,缓存友好性远超基于红黑树的multiset,同时在内存占用上也更加紧凑。本文将从底层实现原理、性能对比分析、与multiset和flat_set的区别、典型使用场景以及基本用法示例几个方面,带你彻底搞懂这个有序数组容器的核心价值,帮助你在读多写少的场景中做出正确的容器选型决策。

C++23在标准库中引入了一组新的容器适配器,其中就包括std::flat_multiset。它是一个基于有序数组的关联容器,允许存储重复元素,并始终保持元素有序。与基于红黑树实现的std::multiset不同,flat_multiset内部采用连续内存的vector作为底层结构,这一设计变化带来了完全不同的性能特征。本文将深入剖析它的实现原理、性能表现和使用场景。

C++23的std::flat_multiset有什么优势?有序数组容器深度解析

一、底层实现原理:有序数组如何支撑关联容器

std::flat_multiset实际上是一个容器适配器,它并不自己管理内存,而是包装了一个底层序列容器(默认是std::vector)。所有元素在这个vector中按照比较规则(默认是std::less)保持有序排列。当你插入一个元素时,容器通过二分搜索找到合适的插入位置,然后将该位置之后的元素整体后移一位,腾出空间放入新元素;删除元素时则将后续元素整体前移一位。

这种实现的直接代价是插入和删除的时间复杂度为O(n),因为移动元素需要线性时间。但换来的是几个显著的优势:第一,查找操作可以退化为纯二分搜索,不需要像红黑树那样沿着指针层层跳转;第二,连续内存布局对CPU缓存极其友好,现代CPU的缓存行预取机制可以充分发挥作用;第三,没有每个节点额外的指针开销,内存占用大幅降低。

标准库还允许你通过模板参数指定底层容器类型,例如使用std::deque代替std::vector,定义形式如下:

#include <flat_set>

// 默认使用std::vector作为底层容器
std::flat_multiset<int> fm1;

// 显式指定底层容器为deque
std::flat_multiset<int, std::less<int>, std::deque<int>> fm2;

二、性能对比:flat_multiset与multiset谁更快

性能是这个容器存在的核心意义。我们先从理论复杂度层面做一个对比:std::multiset的插入、删除、查找都是O(log n);flat_multiset的查找是O(log n),但插入和删除是O(n)。单看复杂度,flat_multiset似乎全面处于劣势,但实际运行中的表现往往相反。

原因在于常数因子和缓存行为。红黑树的每个节点在堆上独立分配,节点之间通过指针连接,查找时CPU需要在内存中不断跳跃,缓存命中率很低。而flat_multiset的元素紧密排列在连续内存中,二分搜索虽然逻辑上跳跃访问,但当数据量在几万到几十万级别时,大部分数据都能装进CPU的L2、L3缓存,实际查找速度通常能达到multiset的2到5倍。

内存占用差距同样明显。存储一百万个int类型元素,multiset每个节点除了4字节数据外还要存储颜色标记、三个指针以及内存分配器的开销,实际内存占用可能是vector版本的5到10倍。以下是两种容器的对比表格:

特性std::multisetstd::flat_multiset
底层结构红黑树有序数组(vector)
插入复杂度O(log n)O(n)
删除复杂度O(log n)O(n)
查找复杂度O(log n)O(log n)
迭代器稳定性插入删除不失效插入删除可能失效
内存占用高(含指针开销)低(紧凑连续)
缓存友好性优秀

需要注意的一个关键点是迭代器失效规则。flat_multiset本质上依赖vector,任何可能导致vector重新分配内存的操作都会使所有迭代器失效,这一点和multiset形成鲜明对比,写代码时务必小心悬空迭代器问题。

三、与flat_set的区别以及典型使用场景

flat系列容器一共有四个:flat_setflat_mapflat_multisetflat_multimap。flat_multiset与flat_set的区别在于是否允许重复键值。当你调用flat_set的insert插入已存在的元素时,插入会被忽略并返回已有元素的位置;而flat_multiset会正常插入,重复元素会相邻存放。

基于这些特性,flat_multiset最适合的场景是读多写少的数据集合。典型例子包括:配置数据的加载与查询(启动时构建一次,运行期间只读)、排行榜系统(周期性批量重建,期间高频查询名次)、词典或索引表(预排序后大量查找)、以及需要频繁进行范围查询的统计场景。这些场景的共同点是查找和遍历操作远多于插入删除。

反过来,如果你的程序需要频繁地随机插入和删除元素,例如实时消息队列的去重管理,那么flat_multiset的O(n)插入会成为性能瓶颈,此时坚持使用传统的multiset才是正确选择。选型的核心判断依据就是读写比例:读操作占比超过九成的场景,flat容器几乎总是更优解。

四、基本用法示例

flat_multiset的接口与multiset几乎完全一致,学习成本几乎为零。下面的示例展示了插入、重复元素处理、查找和范围查询的基本操作:

#include <iostream>
#include <flat_set>

int main() {
    std::flat_multiset<int> fm;

    // 插入元素,允许重复
    fm.insert(5);
    fm.insert(3);
    fm.insert(8);
    fm.insert(3);   // 重复元素也会被插入

    // 元素始终保持有序,重复元素相邻
    for (int v : fm) {
        std::cout << v << " ";  // 输出: 3 3 5 8
    }
    std::cout << "\n";

    // 统计某个值出现的次数
    std::cout << "count(3) = " << fm.count(3) << "\n";  // 输出2

    // 范围查询:查找所有不小于4的元素
    auto it = fm.lower_bound(4);
    while (it != fm.end()) {
        std::cout << *it << " ";  // 输出: 5 8
        ++it;
    }
    return 0;
}

除了逐个插入,flat_multiset还提供了一个针对批量构建的优化技巧:当你已经有一批数据需要全部放入容器时,可以先调用insert_range或使用构造函数直接传入迭代器范围,标准库实现会在内部先排序再去重处理,效率远高于逐个调用insert。

此外,flat_multiset支持通过replace方法直接替换底层容器对象,这在需要从外部已排序数据快速构建容器时非常有用,可以完全跳过逐个插入的O(n log n)开销,实现O(n)的整体构建。

总结

std::flat_multiset代表了C++标准库设计思路的一次务实转变:不再执着于单一结构的理论最优复杂度,而是承认缓存友好的连续内存在真实硬件上的巨大价值。在读多写少的场景下,它用更低的内存占用和更快的查找速度提供了明显优于std::multiset的表现。如果你的编译器已经支持C++23(GCC 13以上、MSVC 19.37以上),不妨在实际项目中尝试这个新容器,相信它会给你带来惊喜。

std::flat_multisetC++23有序容器修改时间:2026-09-02 08:52:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编写,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260902/48827.html,基于非商业使用的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。