C++23中引入了两个新的关联容器--std::flat_map 和 std::flat_set ,其目的是提供更高的性能与更好的缓存局部性(cache locality)。
看到这俩容器,很多人第一个疑问是什么是flat容器?
flat,顾名思义 扁平的意思。
正如我们前面所说的,传统的map和set是基于平衡搜索二叉树(通常为红黑树)实现,通过指针连接的树结构来维护有序性。而flat_map和flat_set则基于顺序容器(通常是 std::vector)实现的扁平化容器(flat container)。
引入它们的主要原因是缓存友好性。在实际性能中,缓存局部性通常比理论上的渐近复杂度(O(N) 与 O(logN))更重要,尤其对于元素数量较少或中等的容器而言。
正是因为其顺序容器的特性,那么其优缺点就很明显了:
核心优势:
-
• 内存连续 → 更好的 CPU 缓存命中率
-
• 小规模数据时,遍历和查找更快
-
• 空间开销更小,没有节点指针等结构
缺点:
-
• 插入、删除需要移动元素 → O(N) 开销
-
• 迭代器在插入删除后会失效
-
• 不适合频繁修改的场景
下面我们通过一个简单的例子来加深对这俩容器的理解:
flat_set_demo:
#include <flat_set>
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
// 构造:自动去重并排序
std::flat_set<int> nums{7, 3, 9, 3, 5};
// => {3, 5, 7, 9}
// 插入新元素(返回 pair<iterator, bool>)
auto [it, ok] = nums.insert(4);
if (ok) std::cout << "插入元素 4 成功\n";
// 使用 emplace 构造元素
nums.emplace(8);
// 一次性插入多个元素(C++23 新增 insert_range)
std::vector<int> extra{1, 2, 3};
nums.insert_range(extra); // 自动排序去重
// 查找与访问
if (nums.contains(5))
std::cout << "找到元素 5\n";
auto lb = nums.lower_bound(4);
std::cout << "lower_bound(4): " << *lb << "\n";
// 随机访问(flat 容器支持随机访问迭代器)
std::cout << "所有元素:";
for (size_t i = 0; i < nums.size(); ++i)
std::cout << nums.begin()[i] << " ";
std::cout << "\n";
// 删除元素
nums.erase(3);
nums.erase(nums.find(9));
std::cout << "删除后:";
for (auto v : nums) std::cout << v << " ";
std::cout << "\n";
// 提取底层容器(C++23)
auto backing = std::move(nums).extract();
std::cout << "底层容器大小: " << backing.size()
<< ", nums 是否为空: " << std::boolalpha << nums.empty() << "\n";
}
在上述代码中,可以看出,可以通过直接用 [] 索引或通过 begin()+n 访问对应元素。
输出如下:
插入元素 4 成功
找到元素 5
lower_bound(4): 4
所有元素:1 2 3 4 5 7 8 9
删除后:1 2 4 5 7 8
底层容器大小: 6, nums 是否为空: true
std::flat_map 是基于 std::vector<std::pair<K,V>> 的有序映射容器。
flat_map_demo:
// 编译:g++ -std=c++23 flat_map_demo.cpp && ./a.out
#include <flat_map>
#include <iostream>
#include <string>
#include <cassert>
int main(){
// 初始化
std::flat_map<int, std::string> dict{
{1, "one"}, {3, "three"}, {2, "two"}
};
// 自动排序为 {1:"one", 2:"two", 3:"three"}
// 插入与更新
dict.emplace(4, "four");
dict.insert_or_assign(2, "TWO"); // 更新已存在键
// 访问
std::cout << "dict[3] = " << dict[3] << "\n";
std::cout << "dict.at(2) = " << dict.at(2) << "\n";
// 遍历
std::cout << "\n所有键值对:\n";
for (const auto& [k, v] : dict)
std::cout << k << " -> " << v << "\n";
// C++23 新增:keys() / values()
const auto& keys = dict.keys();
const auto& vals = dict.values();
std::cout << "\n通过 keys() / values() 访问:\n";
for (size_t i = 0; i < keys.size(); ++i)
std::cout << keys[i] << " => " << vals[i] << "\n";
assert(std::is_sorted(keys.begin(), keys.end()));
}
输出如下:
dict[3] = three
dict.at(2) = TWO
所有键值对:
1 -> one
2 -> TWO
3 -> three
4 -> four
通过 keys() / values() 访问:
1 => one
2 => TWO
3 => three
4 => four
1459

被折叠的 条评论
为什么被折叠?



