std::unordered_map¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++11 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-11-03 |
编译器支持:GCC 4.4+ · Clang 3.1+ · MSVC 16.0+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 用 unordered_map 按名称索引 logger | ev-spdlog-unordered-map-registry-001 |
| 用 unordered_map 建立字符串视图到偏移量的索引 | ev-mold-unordered-map-strings-002 |
| 用 unordered_map 作为缓存主存储 | ev-drogon-unordered-map-cache-003 |
本站证据页:ev-drogon-unordered-map-cache-003 · ev-mold-unordered-map-strings-002 · ev-spdlog-unordered-map-registry-001
解决什么问题¶
需要按键快速取回值,但不关心遍历顺序时,std::map 的红黑树给出 O(log n) 查找并维护排序,代价是每次操作都要比较、旋转和额外节点分配。数据量一大,对数因子和指针追逐就成为热点。
std::unordered_map<Key, T> 用哈希表提供平均 O(1) 的查找、插入和删除,不保证元素顺序。它适合“按名字取对象”“计数器”“缓存”这类只关心键值映射的场景。标准提案为 N1456。
基础用法¶
operator[] 在键不存在时插入值初始化的元素,因此读取会意外扩容;只读访问用 at(),查找用 find() 或 count()。emplace() 返回 pair<iterator, bool>,second 表示是否真正插入。
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
int main() {
std::unordered_map<std::string, int> counts;
counts.reserve(8);
counts["apple"] = 2;
counts["pear"] += 1;
auto result = counts.emplace("apple", 5);
counts.insert(std::make_pair(std::string("banana"), 3));
std::cout << counts.size() << ' ' << counts.at("apple") << ' '
<< (result.second ? "new" : "exists") << '\n';
std::cout << (counts.find("pear") != counts.end() ? "found" : "missing")
<< ' ' << counts.count("grape") << '\n';
counts.erase("pear");
std::cout << counts.size() << ' '
<< (counts.count("pear") ? "kept" : "erased") << '\n';
try {
counts.at("missing");
} catch (const std::out_of_range&) {
std::cout << "out_of_range" << '\n';
}
std::vector<std::pair<std::string, int>> ordered(counts.begin(),
counts.end());
std::sort(ordered.begin(), ordered.end());
for (std::size_t i = 0; i < ordered.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << ordered[i].first << '=' << ordered[i].second;
}
std::cout << '\n';
}
输出 3 2 exists、found 0、2 erased、out_of_range 与 apple=2 banana=3。示例覆盖预留容量、emplace 不覆盖、find/count、erase、at 越界异常,以及把无序结果复制到 vector 后排序输出。完整版本见 examples/cpp11/unordered_map.cpp。
进阶用法¶
预留容量。 已知元素数量时先 reserve(n),把重哈希次数降到最低;max_load_factor() 控制桶的拥挤程度,调高节省内存但增加冲突,调低则相反。
避免不必要的临时对象。 C++17 的 try_emplace(key, args...) 只在键不存在时构造值,insert_or_assign 则显式表达“插入或覆盖”。C++17 还提供节点句柄,可以在不重新分配的情况下把元素从一个容器搬到另一个容器。
C++20 增加 contains() 与异构查找。 contains(key) 比 find() != end() 更直接;当哈希器支持透明查找时,可以用 std::string_view 直接查 std::string 键,避免临时字符串。
自定义哈希与相等。 自定义类型作为键时,提供 std::hash 特化或传入哈希/相等函数对象。哈希质量直接决定冲突率;对不可信输入,还要考虑哈希碰撞攻击,标准库实现通常不保证抗碰撞。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| spdlog | include/spdlog/details/registry.h#L111-L113 |
按名称索引 shared_ptr<logger> |
注册表只需按名字取回对象,不需要有序遍历,哈希查找是自然选择 |
| mold | src/mold.h#L731-L733 |
用 string_view 键建立字符串驻留索引 |
键指向已有稳定存储,避免复制字符串,同时保持均摊常数时间查找 |
| drogon | lib/inc/drogon/CacheMap.h#L450-L450 |
用模板 unordered_map 作为缓存主存储 |
缓存只按键读写、不要求顺序,过期清理由独立时间轮承担 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。三处都在生产路径上,覆盖注册表、字符串驻留和缓存三种形态。
陷阱与边界¶
operator[] 会插入。 读取不存在的键会插入默认值并可能触发重哈希;只读场景用 at()(越界抛 std::out_of_range)或 find()。
重哈希会使迭代器失效。 插入导致重哈希时,所有迭代器失效,但指向元素的引用和指针仍然有效;删除只使被删元素的迭代器失效。
遍历顺序不稳定。 顺序取决于哈希、桶数和实现,不能依赖;需要稳定输出时先复制到 std::vector 再排序。
键必须可哈希且不可变。 键在容器内是 const,不能通过迭代器修改;自定义键要保证相等与哈希一致。
std::string_view 键的生命周期。 视图不拥有字符存储,底层字符串销毁后键就悬空;用于驻留表时要确保存储活得比容器久。
哈希碰撞可能被利用。 攻击者构造大量同桶键会把平均 O(1) 退化为 O(n);处理不可信输入时应使用带随机种子的哈希或限制输入。
库联动¶
<unordered_map>:容器本体;配套的<unordered_set>提供集合版本。<functional>:std::hash、std::equal_to是默认哈希与相等策略,自定义键可特化或传入函数对象。<string_view>:C++17 起常用作只读键,前提是底层存储生命周期足够长。- abseil / TBB:
absl::flat_hash_map用开放寻址降低指针追逐,tbb::concurrent_unordered_map提供并发插入查找。 - spdlog / mold / drogon:分别展示名称注册表、字符串驻留表与缓存主存储三种生产用法。
横向对比¶
| 方案 | 平均查找 | 有序遍历 | 迭代器稳定性 | 主要限制 |
|---|---|---|---|---|
std::unordered_map |
O(1) | 否 | 重哈希使迭代器失效 | 哈希质量与碰撞攻击,顺序不稳定 |
std::map |
O(log n) | 是 | 插入删除不影响其他 | 每次操作有树维护成本 |
absl::flat_hash_map |
O(1) | 否 | 重哈希使引用失效 | 非标准库,依赖 Abseil |
std::vector 线性查找 |
O(n) | 是 | 插入可能使全部失效 | 元素少时更快,元素多时不可接受 |
跨标准演进:C++11 通过 N1456 引入无序关联容器;C++17 增加节点句柄、try_emplace 与 insert_or_assign;C++20 增加 contains() 与透明哈希查找。