最稳方案是std::list+std::unordered_map组合:map存key到list迭代器的映射,实现o(1)查找、移动与删除;需同步操作map和list避免悬空迭代器,构造时校验capacity合法性。

用 std::list + std::unordered_map 实现 LRU 缓存,是 C++ 里最稳、最不易翻车的方案。手写双向链表或裸指针管理,90% 的崩溃和迭代器失效都源于此。
为什么不能只用 std::list 模拟队列?
常见错误是把 std::list 当成 FIFO 队列用:每次 get() 都从头遍历找 key,再 splice 到 front —— 这直接退化成 O(n) 查找,完全失去缓存意义。
正确做法是让 std::unordered_map 的 value 存 std::list<pair int>>::iterator</pair>,不是指针,不是 int*,必须是迭代器类型。这样才能:
-
map[key]直接拿到节点位置,list.splice(list.begin(), list, it)移动到头部,O(1) - 插入新节点时,
list.emplace_front(key, value)得到新迭代器,存进 map - 避免
it = list.erase(it)后 map 里还留着已失效的迭代器
erase 和 pop_back 必须同步操作
淘汰最久未用项(即 list.back())时,只删 list 或只删 map 都会出事:
-
map.erase(list.back().first); list.pop_back();看似顺,但如果该 key 已被put()覆盖过,map里可能已无此 key,erase不报错但没效果,pop_back却照常执行 → list 少一节点,map 多一个悬空迭代器 - 后续任意
get()解引用这个迭代器,就是 UB:崩溃、随机值、静默数据污染都可能 - 正确写法是先
auto it = map.find(key),确认存在后再同时操作两边;淘汰逻辑建议抽成独立函数,比如evict_oldest()
构造函数必须校验容量合法性
LRUCache(int capacity) 传入 0 或负数很常见,但不处理会导致整个逻辑失序:
-
list.size() > capacity永远为 false,淘汰分支永不触发 - 有些实现用
capacity_ = std::max(1, cap)强行兜底,掩盖了非法输入,后期扩容策略变更时难定位 - 推荐做法:构造函数第一行就
if (capacity ,之后所有 <code>put()直接 return,get()直接 return -1(或对应默认值) - 单元测试必须覆盖
LRUCache(0)和LRUCache(-1),别信“运行没崩”
最容易被忽略的是 std::list::splice 的迭代器失效规则:它不会使被移动节点的迭代器失效,但源容器的 end() 迭代器会变 —— 如果你用 end() 做边界判断又没及时刷新,后续遍历可能越界。实际编码中,少依赖 end(),多用 empty() 或显式 size 对比。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











