缓存项结构体必须存储时间戳而非依赖定时器猜测过期:插入时用steady_clock计算并保存expire_time,访问时仅比较当前时间与该时间戳,避免重复调用系统时钟和迭代器失效问题。

缓存项结构体里必须存时间戳,不能只靠定时器“猜”过期
很多初学者用 std::chrono::steady_clock::now() 在读取时临时计算是否过期,看似省事,但会带来两个问题:一是每次访问都调用系统时钟,开销不小;二是如果缓存项被反复读取但没更新,它的“逻辑过期时间”其实应该固定,而不是每次重新算。正确做法是在插入时就计算并保存 expire_time,类型用 std::chrono::time_point<:chrono::steady_clock></:chrono::steady_clock>,避免用 system_clock(可能受系统时间调整影响)。
- 插入时:用
steady_clock::now() + std::chrono::seconds(60)算出绝对过期时刻,存进结构体 - 访问时:只做一次比较
steady_clock::now() ,不重复构造 time_point - 别用
time_t或毫秒整数存——精度丢失、易溢出、跨平台行为不一致
用 std::shared_ptr 管理缓存值,避免深拷贝和悬挂指针
缓存项的值可能是大对象(比如 std::vector<uint8_t></uint8_t> 或自定义结构体),直接存值会导致频繁复制;存裸指针又得手动管理生命周期。用 std::shared_ptr 是最平衡的选择:插入时构造一次,多个读取共享同一份内存,且自动释放。
- 缓存结构体定义类似:
struct CacheItem { std::shared_ptr<void> value; std::chrono::time_point<:chrono::steady_clock> expire_time; };</:chrono::steady_clock></void> - 实际使用时建议用模板封装类型,避免
void*带来的类型擦除开销和 unsafe cast - 注意:
shared_ptr的线程安全仅限于引用计数本身,内容读写仍需外部同步(见下一点)
多线程读写必须加锁,但读多写少时优先用 std::shared_mutex
缓存典型场景是大量并发读 + 少量写(插入/更新/淘汰)。用普通 std::mutex 会让所有读操作排队,性能断崖下跌。C++17 起推荐用 std::shared_mutex,支持多个读者同时进入,写者独占。
- 读操作用
std::shared_lock<:shared_mutex></:shared_mutex> - 写操作用
std::unique_lock<:shared_mutex></:shared_mutex> - 注意:Windows 上 VS2015+ 支持,但 GCC 8+ 才完整实现
shared_mutex;若环境受限,退回到std::mutex+ 读写分离设计(如双缓冲) - 别在锁内做耗时操作(比如网络请求、文件读写),否则阻塞整个缓存
过期检查不能只靠 get(),insert() 和迭代遍历时也得判断
只在 get(key) 里检查过期,会导致过期项长期滞留内存,尤其当某些 key 很少被访问时。更健壮的做法是:在 insert() 前先尝试清理(可选),并在后台或下次访问时惰性淘汰;但最简单可靠的是——每次从 map 中取出项后,立即检查 expire_time,过期则直接 erase 并返回空。
- 示例逻辑:
auto it = cache_map.find(key); if (it != cache_map.end() && steady_clock::now() >= it->second.expire_time) { cache_map.erase(it); return std::nullopt; } - 不要在 erase 后还访问
it->second.value—— 迭代器已失效 - 批量清理(如 LRU 淘汰)时,务必用
erase()返回的下一个有效迭代器,避免++it失效
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











