
怎么定义一个不绑定具体存储的 KeyValueStore 接口
核心是把读写删查行为抽象成纯虚函数,不暴露任何后端细节(比如 Redis 的连接池、SQLite 的 sqlite3* 指针)。接口里只留最通用的操作:键必须是 std::string,值统一用 std::string 或 std::span<const std::byte></const> —— 别过早引入序列化逻辑,那是上层的事。
常见错误是把超时、重试、压缩开关塞进接口参数,结果每个实现都要处理一堆可选逻辑。应该把这些下放到具体 backend 类里,接口只管“存/取/删/是否存在”四件事:
bool put(const std::string& key, const std::string& value)-
bool get(const std::string& key, std::string& out_value)(返回false表示键不存在) bool remove(const std::string& key)bool exists(const std::string& key)
Redis / SQLite / Memory 实现时怎么处理线程安全差异
内存版(MemoryStore)用 std::shared_mutex 就够了,读多写少场景下比 std::mutex 更轻;但 Redis 后端本质是网络调用,本身已串行化,加锁反而拖慢吞吐——你锁的是本地连接对象,不是远端服务,所以只在复用 redisContext* 时保护连接池,别对每个 put() 加锁。
SQLite 更麻烦:它默认是 serialized 模式,但如果你开了 WAL,多个连接可并发读,写仍需独占。这时候接口层不该替用户决定要不要开 WAL,而是在构造 SqliteStore 时传入 flags(如 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX),让使用者自己权衡。
立即学习“C++免费学习笔记(深入)”;
容易踩的坑:用 std::mutex 包裹整个 get(),结果 Redis 版本因为网络延迟导致锁持有太久,拖垮其他线程。
为什么不要在接口里暴露 batch_put 或 scan
不是功能不重要,而是它们在不同后端语义差异太大:Redis::mset 是原子的,SQLite 的批量插入需要事务包装,而内存版的“批量”只是 for 循环——如果强行统一成一个 batch_put 接口,使用者会误以为所有实现都具备相同原子性或性能特征。
更实际的做法是:只在具体 backend 类里提供扩展方法,比如 RedisStore::pipeline_put() 或 SqliteStore::transactional_batch(),名字带后端标识,不污染通用接口。这样调用者一眼就知道这是 Redis 特有的能力,不会跨后端移植时掉坑里。
同理,scan 在 Redis 是游标式,在 SQLite 是 SELECT key FROM kv WHERE key LIKE ?,内存版可能直接遍历 map —— 语义不一致,硬统一只会让边界 case 越来越难测。
如何避免用户误用指针生命周期导致崩溃
接口里所有返回值必须是值语义:get() 用输出参数(std::string& out_value)而不是返回 std::string*;构造函数接收的配置对象也该是值类型或 const&,别接受裸指针。
典型翻车现场:有人传入临时 std::string("redis://127.0.0.1") 给 RedisStore 构造函数,而 store 内部存了 const char* 指向它内部数据——临时对象一销毁,后续 connect 就访问野指针。
解决办法很简单:配置项全用 std::string 成员,构造时用 std::move 或拷贝;所有字符串输入参数声明为 const std::string&,杜绝隐式转换到 const char*。
这个点看着琐碎,但线上 core dump 最常出在这儿——不是算法错,是 string 生命周期没管住。










