Immer库持久化与序列化技术详解
前言
Immer是一个优秀的C++不可变数据结构库,它通过结构共享(Structural Sharing)技术实现了高效的内存使用。在实际应用中,我们经常需要将这些数据结构序列化保存到磁盘或通过网络传输。本文将深入探讨Immer库中的持久化序列化功能,特别是如何使用immer::persist
来保持数据结构间的结构共享关系。
基础序列化的问题
让我们从一个简单的例子开始。假设我们有一个文档类型document
,它包含两个Immer向量:
struct document {
immer::vector<int> ints;
immer::vector<int> ints2;
};
如果我们创建两个向量v1
和v2
,其中v2
是从v1
派生而来并与之共享数据:
auto v1 = immer::vector<int>{1, 2, 3};
auto v2 = v1.push_back(4).push_back(5).push_back(6);
使用常规的序列化方法(如cereal库)会得到类似这样的JSON:
{"value0": {"ints": [1, 2, 3], "ints2": [1, 2, 3, 4, 5, 6]}}
这种序列化方式存在明显的问题:它完全丢失了ints
和ints2
之间的结构共享信息,导致反序列化后会创建两个完全独立的向量,失去了Immer的核心优势。
使用Immer持久化池
Immer提供了更智能的序列化方式——通过持久化池(Pools)来保持结构共享。首先,我们需要让document
类型兼容boost::hana
:
struct document {
immer::vector<int> ints;
immer::vector<int> ints2;
};
BOOST_HANA_ADAPT_STRUCT(document, ints, ints2);
然后使用immer::persist
进行序列化:
immer::persist::to_json(stream, doc);
生成的JSON会是这样:
{
"value": {
"ints": 0,
"ints2": 1
},
"pools": {
"vector<int>": {
"0": {
"size": 3,
"values": [1, 2, 3]
},
"1": {
"size": 6,
"tail_values": [4, 5, 6],
"base": 0
}
}
}
}
这种表示方式有几个关键优势:
- 值部分只包含容器引用ID
- 所有容器数据集中在pools部分
- 共享的结构被明确表示(如
base
字段) - 反序列化后能恢复原始的结构共享关系
自定义序列化策略
对于更复杂的场景,Immer允许我们定义自定义策略。考虑以下包含嵌套类型的文档:
struct extra_data {
immer::vector<std::string> comments;
};
struct doc_2 {
immer::vector<int> numbers;
extra_data extra;
};
我们可以定义策略类来控制池的命名和行为:
struct doc_2_policy {
static auto get_pool_types() {
return std::make_tuple(
immer::persist::pool_type<immer::vector<int>>{},
immer::persist::pool_type<immer::vector<std::string>>{}
);
}
template <typename Archive>
static void save(Archive& ar, const doc_2& doc) {
ar(cereal::make_nvp("doc2_value", doc));
}
static std::string get_pool_name(immer::persist::type<immer::vector<int>>) {
return "numbers_pool";
}
static std::string get_pool_name(immer::persist::type<immer::vector<std::string>>) {
return "comments_pool";
}
};
使用这个策略序列化会生成结构清晰的JSON,其中嵌套的容器也能正确保持引用关系:
{
"doc2_value": {
"numbers": 0,
"extra": {"comments": 1}
},
"pools": {
"numbers_pool": {...},
"comments_pool": {...}
}
}
技术实现细节
Immer的持久化系统有几个值得注意的实现细节:
-
输入输出池分离:类似于cereal的InputArchive/OutputArchive设计,Immer区分了用于保存和加载的池
-
树结构表示:JSON中直接表示了构成Immer容器的树结构节点
-
引用计数:共享的节点只存储一次,通过引用机制保持关系
-
类型安全:通过模板元编程确保类型正确的序列化和反序列化
最佳实践建议
-
对于简单用例,优先使用
BOOST_HANA_ADAPT_STRUCT
自动适配 -
复杂场景下定义明确的策略类,控制池命名和序列化行为
-
注意嵌套类型的序列化,确保所有容器类型都被策略覆盖
-
考虑数据版本兼容性,特别是长期持久化的场景
-
性能敏感场景可以评估二进制序列化替代JSON
总结
Immer的持久化系统提供了一种优雅的方式来序列化不可变数据结构,同时保持其核心的结构共享特性。通过池的概念和灵活的策略系统,开发者可以平衡便利性和控制力,满足从简单到复杂的各种序列化需求。这种设计不仅适用于应用状态持久化,也为撤销历史、剪贴板等高级功能提供了坚实的基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考