缓存应使用包装结构体(如CacheEntry)分离数据与版本号,避免污染业务模型;读多写少场景用std::shared_mutex提升并发性能;版本号推荐递增整数或steady_clock::now();淘汰策略需避免误删高版本低频数据。

缓存类需要支持版本号检查,核心是分离数据与元信息
直接把 version 塞进业务对象里会污染模型,也难统一校验。正确做法是用包装结构体或 pair 把数据和版本号绑在一起,比如 std::pair<t uint64_t></t> 或自定义 CacheEntry。这样读取时能原子性地拿到值和对应版本,避免竞态——比如缓存刚被更新、但旧版本还没过期,这时仅靠 key 查不到变更,必须连带版本一起读。
示例结构:
template<typename T>
struct CacheEntry {
T value;
uint64_t version;
std::chrono::steady_clock::time_point last_access;
};
注意:不要用 time_t 或毫秒时间戳做版本号,它不单调、不可比;推荐用递增整数(如全局原子计数器)或写入时 std::chrono::steady_clock::now() ——后者更易调试,但需确保时钟单调且精度足够。
使用 std::shared_mutex 保护读多写少场景
缓存典型特征是读远多于写,std::shared_mutex(C++17 起)比 std::mutex 更合适:多个线程可同时加共享锁读,写操作独占排他锁。若用错成普通互斥量,高并发下会严重拖慢读性能。
立即学习“C++免费学习笔记(深入)”;
常见错误:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 在
get()中用了lock_guard(独占锁),而不是shared_lock - 写操作没用
unique_lock,导致并发写冲突 - 忘记在
put()后更新last_access时间,使 LRU 淘汰失效
关键代码片段:
std::shared_mutex mtx_;
std::unordered_map<std::string, CacheEntry<T>> cache_;
// 读
std::shared_lock<std::shared_mutex> lock(mtx_);
auto it = cache_.find(key);
if (it != cache_.end()) {
it->second.last_access = std::chrono::steady_clock::now();
return {it->second.value, it->second.version};
}
// 写
std::unique_lock<std::shared_mutex> lock(mtx_);
cache_[key] = {value, next_version_++, std::chrono::steady_clock::now()};
版本号验证失败时,该返回 stale 数据还是抛异常?
取决于调用方语义需求。例如配置缓存通常允许读到旧版(stale-while-revalidate),而金融交易状态必须严格一致。因此接口要暴露策略选择:
-
get_if_version_match(const std::string& key, uint64_t expected_version):只在版本匹配时返回,否则返回std::nullopt -
get_with_version(const std::string& key):无条件返回当前值+版本,由上层判断是否过期
别写成“自动重拉新数据”——那已超出缓存职责,应由业务层控制。缓存只负责“提供当前持有的数据及其可信度标识(即版本)”。另外,版本号比较建议用 >= 而非 ==,因为某些场景下“不低于某版本”即满足一致性要求。
淘汰策略与版本号的耦合风险
LRU 或 TTL 淘汰逻辑如果只看访问时间,可能误删高版本但低频访问的数据。更稳妥的做法是:淘汰前先检查该 entry 的版本是否已被更高版本覆盖(比如通过一个全局 max_version_seen 记录)。但要注意——这引入了额外同步开销。
简单方案是不做跨 key 版本联动,只保证单 key 内部版本单调递增;复杂系统则需配合外部版本协调器(如 etcd watch + revision)。日常项目中,90% 场景只需在 put() 时覆盖同 key 的老 entry 即可,版本号天然解决“写覆盖顺序”问题。
真正容易被忽略的是:缓存清理时机。不要在 get() 中触发淘汰(会阻塞读),应在 put() 后检查 size 并异步/惰性清理;或者用 std::map 替代 unordered_map,按 last_access 排序便于 O(1) 找最久未用项。

















