真正O(1)的LRU需严格控制迭代器生命周期:先删list再删map,用splice()保持迭代器有效,容量检查须在插入前完成,裸指针需手动安全析构。

直接用 std::list + std::unordered_map 就能实现真正 O(1) 的 get() 和 put(),但必须严格控制迭代器生命周期——这是唯一决定你代码是否稳定的关键。
为什么不能先删 map 再删 list?
因为 std::unordered_map 存的是 std::list<pair int>>::iterator</pair>,一旦你调用 list.erase(it),那个迭代器立刻失效。如果此时还拿着它去访问 map[key] 或调用 map.erase(key),行为未定义(常见表现:崩溃、随机值、ASan 报 use-after-free)。
正确顺序只能是:
- 先用
cache.back().first拿到待淘汰 key - 再调用
cache.pop_back() - 最后调用
map.erase(key)
std::list::splice() 比 erase+push_front 更安全
手动 erase() 再 push_front() 容易漏掉更新 map 中的迭代器;而 splice() 可以把节点原地移到头部,不改变其地址,所有指向它的迭代器依然有效。
立即学习“C++免费学习笔记(深入)”;
示例写法:
auto it = map[key]; cache.splice(cache.begin(), cache, it); // it 仍有效,可直接用于 map[key] = it;
注意:splice() 第三个参数是迭代器,不是值;且只适用于同一容器内移动。
容量检查必须在插入后立即做
很多实现写成 “先 push_front(),再判断 size() > capacity”,这会导致缓存短暂超出容量(比如 capacity=2 时实际存了 3 个)。虽然逻辑上最终会删掉一个,但中间状态可能被并发访问或触发断言失败。
更稳妥的做法是:
- key 不存在时:插入前先判断
cache.size() == capacity - 若满,先淘汰尾部(
pop_back()+map.erase()),再插入新节点 - 避免任何时刻
cache.size() > capacity
智能指针不是必须的,但 raw pointer 要小心析构
用裸指针(如 DNodeList<int int>*</int>)配合手动 new/delete 是可行的,但必须确保:
- 所有路径(包括异常分支)都释放节点
-
~LRU()中遍历map并delete每个 value - 不能依赖
std::list自动析构节点——它只析构 pair,不 delete 指针
用 std::shared_ptr + weak_ptr 能自动管理生命周期,但引入引用计数开销;对高频缓存场景,裸指针 + 明确所有权仍是主流选择。
最容易被忽略的点:std::list 迭代器在节点被 erase() 后立即失效,哪怕只是把它传给 std::cout 都是未定义行为——这不是“偶尔出错”,而是 C++ 标准明确定义的悬垂访问。


















