跳转至

flat_set

template<  
    class Key,  
    class Compare = std::less<Key>,  
    class KeyContainer = std::vector<Key>  
> class flat_set;

类模板 flat_set 表现为对作为 KeyContainer 类型的对象而传递的底层有序容器的包装器。

1 flat_set 和 set 区别

setstd::flat_set 都使用有序的平衡二叉搜索树,但 flat_set 底层数据存在 KeyContainer 指定的容器里面,默认是 vector。 优点: 1. 迭代更快。 2. 查询更快。同样是log2(n),但常数更小。 缺点: 1. 插入、删除慢。插入和删除都会造成旧迭代器的失效,因为不论是插入还是删除,都会伴随着大部分元素的移动,保存的旧迭代器很难不失效

std::flat_set 是 C++23 引入的一个新容器,位于 <flat_set> 头文件中。它与传统的 std::set 类似,都是有序关联容器,但底层实现和性能特性有显著区别。以下是关键差异和适用场景:


2 底层数据结构

容器 底层结构 内存布局
std::set 红黑树(平衡二叉搜索树) 节点分散分配(指针链接)
std::flat_set 动态数组(如 std::vector 连续内存存储
  • std::set:每个元素独立分配节点,通过指针连接,适合频繁插入/删除。
  • std::flat_set:元素存储在连续内存中(类似 std::vector),缓存友好,但插入/删除成本较高。

3 性能特点

操作 std::set std::flat_set
查找 (find) O (log n) O (log n)(二分查找,更快因缓存局部性)
插入/删除 O (log n) O (n)(需移动后续元素)
遍历 较慢(指针跳转) 极快(连续内存,CPU 缓存命中率高)
内存占用 较高(每个节点含指针开销) 更低(无指针开销,仅存储元素)

4 使用场景

4.1 适合 std::flat_set 的情况:

  1. 频繁查找/遍历,极少修改
  2. 例如:配置表、只读或初始化后固定的数据集。
  3. 需要极致缓存友好性
  4. 对性能敏感的场景(如游戏、高频交易)。
  5. 内存受限环境
  6. 减少内存碎片和开销(嵌入式系统)。

4.2 适合 std::set 的情况:

  1. 频繁插入/删除
  2. 如动态更新的实时数据集合。
  3. 元素较大或不可移动
  4. std::flat_set 的插入/删除会导致元素移动。

5 代码示例

5.1 基本用法(与 std::set 类似)

#include <flat_set>
#include <iostream>

int main() {
    std::flat_set<int> fs = {3, 1, 4, 1, 5}; // 自动排序去重
    fs.insert(2); // 插入(可能触发元素移动)

    for (int x : fs) { // 遍历速度极快
        std::cout << x << " "; // 输出: 1 2 3 4 5
    }

    auto it = fs.find(4); // 二分查找
    if (it != fs.end()) {
        std::cout << "\nFound: " << *it;
    }
}

5.2 与 std::set 的对比

#include <set>
#include <flat_set>
#include <vector>

void compare() {
    std::set<int> s = {3, 1, 4};
    std::flat_set<int> fs = {3, 1, 4};

    // 插入性能差异
    s.insert(2);      // O(log n),无数据移动
    fs.insert(2);     // O(n),可能需要移动元素

    // 内存占用对比
    std::vector<int> v = {1, 2, 3};
    std::cout << sizeof(fs) << "\n"; // 类似 vector(通常 24 字节)
    std::cout << sizeof(s) << "\n";  // 包含树结构开销(通常 48 字节)
}

6 注意事项

  1. 插入/删除成本
    std::flat_set 的修改操作需要移动元素,可能触发内存重新分配。
  2. 迭代器稳定性
    修改操作会使所有迭代器、指针、引用失效(类似 std::vector)。
  3. 自定义排序
    需在构造函数中指定比较器(与 std::set 相同):
    auto cmp = [](int a, int b) { return a > b; };
    std::flat_set<int, decltype(cmp)> fs(cmp);
    

7 总结

  • 选择 std::flat_set:优先考虑查找/遍历性能、内存紧凑性,且数据修改较少时。
  • 选择 std::set:需要频繁插入/删除或元素稳定性时。

std::flat_set 是 C++ 对性能优化需求的响应,通过牺牲修改效率换取查找和内存优势。