C++标准库没有内置带过期策略的本地缓存容器;推荐用std::shared_mutex分离读写、std::unordered_map+std::set协同管理,后台线程wait_until批量清理,避免轮询和单线程定时器。

直接说结论:C++标准库没有内置的“带过期策略的本地缓存”容器,std::unordered_map + std::chrono + 手动触发清理能跑通,但**不推荐在生产环境用轮询或单线程定时器做过期驱逐**——它无法保证精度、易漏删、并发下难维护。
为什么不能只靠 std::chrono::steady_clock 记时间戳
很多人一上来就给每个缓存项加一个 std::chrono::time_point 字段,读取时判断是否过期。这看似合理,但实际会踩三个坑:
- 每次
get()都要计算now() - stored_time > duration,高频访问下std::chrono::steady_clock::now()虽快,但不是零开销 - 过期项仍占内存,不主动清理会导致缓存持续膨胀,
size()失真 - 没有后台机制,过期项只能等下次访问才被发现,“逻辑过期”和“物理删除”脱节
用 std::thread + std::condition_variable 做轻量级过期调度器
比起依赖第三方定时器库(如 Boost.Asio),用标准库自己搭一个最小可行调度器更可控,也避免引入隐式依赖。核心思路是:后台线程等待最近一个待删项的到期时间,唤醒后批量清理。
- 缓存内部维护一个
std::set,按expire_time排序,键为std::chrono::time_point,值为 key(或 key 的弱引用) - 插入/更新时,把旧的过期时间从
std::set中擦除,插入新的 - 后台线程调用
cv.wait_until(lock, next_expire),唤醒后扫描并移除所有已过期的 key - 注意:
wait_until返回false表示超时未唤醒,此时需重新计算最近过期时间
示例关键片段:
立即学习“C++免费学习笔记(深入)”;
auto now = std::chrono::steady_clock::now();
auto it = expiry_set.lower_bound(now);
while (it != expiry_set.begin()) {
--it;
if (it->first <= now) {
cache_map.erase(it->second); // 物理删除
expiry_set.erase(it);
} else break;
}
std::shared_ptr 和 std::weak_ptr 在缓存生命周期管理中的误用
有人用 std::shared_ptr<t></t> 存值、std::weak_ptr<t></t> 存引用,以为能自动回收。这是错觉:
-
std::weak_ptr不延长对象生命周期,但它本身不触发释放;过期后你仍得手动调lock()判断,再显式erase - 如果缓存项构造开销大(比如含大数组或文件句柄),用
shared_ptr会增加原子计数开销,且无法控制释放时机 - 更稳妥的做法是:值类型直接存储(
T或std::unique_ptr<t></t>),过期时调用析构函数并释放内存——由你完全掌控
并发安全不能只靠 std::mutex 锁整个缓存
锁整个 cache_map 看似简单,但在高并发读多写少场景下,get() 会被 put() 或过期清理阻塞。真实项目中应分层加锁:
- 读操作(
get)只读cache_map,可用std::shared_mutex(C++17)实现多读单写 - 写操作(
put)和过期清理共享同一把独占锁,但清理动作本身尽量短——只做erase,不涉及复杂逻辑 -
expiry_set必须和cache_map共享锁,否则会出现“删了 map 没删 set”或反之的不一致
别忘了:std::shared_mutex 在 Windows MSVC 上直到 VS2019 16.6 才完整支持,Linux GCC 一般没问题。
真正麻烦的从来不是“怎么让一项过期”,而是“怎么让成千上万项在毫秒级误差内被发现、被删、不拖慢读请求、不崩掉内存”。很多开源缓存(如 lru-cache)干脆放弃精确过期,改用惰性+定期扫描+大小限制三重机制——这不是偷懒,是权衡后的务实选择。


















