使用 std::list + std::unordered_map 实现 lru 缓存易翻车,主因是迭代器悬空、rehash 安全性、map 与 list 同步淘汰缺失、自定义 key 哈希/相等逻辑不全、capacity=0 边界处理缺失。

为什么不用 std::list + std::unordered_map 就容易翻车
因为 std::list::erase(iterator) 是常数时间,但如果你存的是 list::iterator 到 unordered_map 里,一旦发生 rehash,迭代器不会失效,但你得确保没在 erase 后还用它——而更隐蔽的问题是:多次 splice 或 push_front 后,如果 map 里存的 iterator 指向已被移动的节点,行为未定义。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 必须用
list::iterator作为 value 存进unordered_map,不能存指针或索引 - 每次
get(key)命中后,先list.erase(it)再list.push_front(),然后更新 map 中的 iterator - 别用
list::splice优化移动——它看似高效,但容易因迭代器悬空出错;老老实实erase + push_front更稳 -
unordered_map的 key 类型必须支持哈希和等号比较,自定义类型要显式提供std::hash特化或 lambda(C++20 起支持)
std::unordered_map::erase(key) 和 std::list::remove_if 别混着清缓存
淘汰策略不是“删掉最久未用的”,而是“删到 size ≤ capacity”。常见错误是写个 while (list.size() > cap) { list.pop_back(); },但此时 map 里还留着已删除节点的 key → 内存泄漏 + 后续 get 返回脏数据。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 淘汰必须同步操作 list 和 map:每次
pop_back()后,立刻用尾部节点的 key 调用map.erase(key) - 不要用
list::remove_if清理——它不返回被删元素,你拿不到 key 去删 map - 如果用 C++17,可借助
extract()从 map 拿出 node,再删 list 节点,避免两次查找;但多数场景没必要,直接 erase + pop 更直白
自定义类型作 key 时,std::hash 特化漏掉 operator== 就会查不到
比如用 struct Key { int a; std::string b; }; 当 key,只写了 std::hash<key></key> 特化,却没重载 operator==,unordered_map::find() 可能返回 end() 即使 key 实际存在——因为哈希桶内比对靠 ==,不是靠哈希值。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 只要自定义 key,必须同时提供完整哈希 + 相等逻辑:要么重载
operator==,要么给unordered_map传入二元谓词模板参数 - 哈希函数别用
std::hash<int>{}(a) ^ std::hash<string>{}(b)</string></int>—— 异或会抵消相同 bit,推荐用std::hash<int>{}(a) ^ (std::hash<string>{}(b) 或 C++17 的 <code>std::hash_combine(需自行实现) - 测试时故意插两个内容相同但地址不同的 key,确认
find能命中
容量为 0 或负数时,put 不该崩溃,但很多人忘了检查
LRU 缓存初始化传入 capacity = 0 是合法输入,意味着“禁止缓存”,所有 put 都应跳过存储,所有 get 都返回 -1。但不少实现一上来就构造 list 和 map,没判断 capacity,导致空操作引发未定义行为(比如对空 list 调用 front())。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 构造函数里立刻保存
capacity,并在put开头加if (capacity -
get也得先判capacity ,再查 map,否则可能访问空容器 - 别依赖 “capacity 不会为 0” —— LeetCode 测试用例明确包含
LRUCache(0)
最易被忽略的是迭代器生命周期管理:list 迭代器只有在对应节点被 erase 或 list 被析构时才失效;但如果你把迭代器存 map 里,又在别的地方清空了 list 却忘了清 map,后续任何 get 都会解引用野迭代器。









