最直接的方式是用std::unordered_map存储含过期时间戳的CacheEntry结构体,每次get()时用steady_clock实时校验并清理;不可依赖插入时间或std::map按时间排序,多线程需shared_mutex分段锁。

用 std::unordered_map + std::chrono 手动管理过期时间
最直接的方式是把 key、value 和过期时间戳三者打包存进一个结构体,再用 std::unordered_map 管理。每次读取前检查时间戳是否过期,过期则删除并返回空(或抛异常)。
关键点在于:不能只依赖插入时的时间戳,必须在每次 get() 时做实时判断——因为缓存可能长期不被访问,但已过期。
示例结构:
struct CacheEntry {
std::string value;
std::chrono::steady_clock::time_point expires_at;
};
使用时注意:
-
expires_at必须用std::chrono::steady_clock,避免系统时间回拨导致误判 - 过期判断用
now() ,别用>,防止刚过期那纳秒被跳过 - 写
get()时建议先查再删,避免重复计算now()导致逻辑偏差
为什么不用 std::map 按时间排序自动淘汰?
有人想用 std::map<:chrono::time_point key> 维护过期队列,看似能 lower_bound() 批量清理。但实际问题很多:
- 同一个时间点可能有多个 key 过期,
std::map的 key 必须唯一,得套一层std::multimap或用std::vector+ 手动扫描 - 过期时间不是插入顺序,频繁插入/删除会破坏局部性,cache miss 率上升
- 无法通过 key 快速定位 entry,
get(key)变成 O(n) 查找,失去哈希表意义
结论:按时间组织只适合后台惰性清理(比如每 N 次操作扫一次),不能替代主索引。
std::shared_ptr 配合自定义删除器实现延迟释放
如果 value 是大对象(如 std::vector),且希望过期后立即释放内存,可以借助 std::shared_ptr 的自定义删除器:
auto deleter = [](CacheEntry* p) {
if (std::chrono::steady_clock::now() > p->expires_at) {
delete p;
} else {
// 过期未到,仍需保留
delete p;
}
};
但这样并不真正“延迟”——它只是把释放时机从 erase() 移到 shared_ptr 析构。真正可控的延迟释放需要配合定时器线程或事件循环,C++ 标准库不提供,得靠 std::thread + std::condition_variable 自行调度。
更现实的做法是:在 get() 中发现过期就主动 erase(),让 value 的析构函数立刻执行;别指望智能指针替你做时间判断。
多线程环境下必须加锁,但别锁整个 map
std::unordered_map 不是线程安全的,读写并发会崩溃。但为每次 get() 或 set() 锁住整个容器,性能极差。
推荐做法:
- 用
std::shared_mutex(C++17):读多写少场景下,多个get()可并发,set()和过期清理独占 - 或者分段锁(shard lock):把 key 哈希后模 N,映射到 N 个独立的
std::shared_mutex和子 map,降低争用 - 绝对不要用
std::mutex包裹整个 map,尤其当 value 析构较慢时,会卡住所有读请求
过期检查本身是只读操作,应尽可能走共享锁路径;只有真正 erase() 时才升级为独占锁。
真正难处理的是“过期但尚未被访问”的 key —— 它们一直占着内存,又无法被自动回收。除非引入后台清扫线程,否则只能靠业务侧控制 TTL 长度和总容量上限。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











