struct_pack简介
struct_pack是一个以零成本抽象,高度易用为特色序列化库。通常情况下只需一行代码即可完成复杂结构体的序列化/反序列化。对于聚合类型,用户无需定义任何DSL,宏或模板代码。struct_pack也支持使用宏来自定义非聚合类型的反射。struct_pack可通过编译期反射自动支持对C++结构体的序列化。其综合性能比protobuf,msgpack大幅提升(详细可以看benchmark部分)。
下面,我们以一个简单的对象为例展示struc_pack的基本用法。
序列化
基本用法
指定序列化返回的容器类型
将序列化结果保存到已有的容器尾部
将序列化结果保存到指针指向的内存中。
多参数序列化
将序列化结果保存到输出流
反序列化
基本用法
从指针指向的内存中反序列化
反序列化(将结果保存到已有的对象中)
多参数反序列化
从输入流中反序列化
部分反序列化
有时候只想反序列化对象的某个特定的字段而不是全部,这时候就可以用部分反序列化功能了,这样可以避免全部反序列化,大幅提升效率。
支持序列化所有的STL容器、自定义容器和optional
含各种容器的对象序列化
对整数启用变长压缩编码
自定义功能支持
用户自定义反射
有时候用户需要支持非聚合的结构体,或者自定义各字段序列化的顺序,这些可以通过宏函数
STRUCT_PACK_REFL(typename, fieldname1, fieldname2 ...)
来支持。
有时,用户需要序列化/反序列化那些private字段,这可以通过函数
STRUCT_PACK_FRIEND_DECL(typenmae)
;来支持。
该宏必须声明在结构体内部,其原理是将struct_pack与反射有关的函数注册为友元函数。
用户甚至可以在
STRUCT_PACK_REFL
中注册成员函数,这极大的扩展了struct_pack的灵活性。
自定义类型的序列化
struct_pack支持序列化自定义类型。
该类型可以被抽象为类型系统中已有的类型
例如,假如我们需要序列化一个第三方库的map类型(absl , boost...):我们只需要保证其符合struct_pack类型系统中对于map的约束即可。
关于自定义类型的更多细节,请见:
该类型不能被抽象为类型系统中已有的类型
此时,我们也支持自定义的序列化,用户只需要自定义以下三个函数即可:
- sp_get_needed_size
- sp_serialize_to
- sp_deserialize_to
例如,下面是一个支持自定义二维数组类型的序列化/反序列化的例子。
序列化到自定义的输出流
该流需要满足以下约束条件:
例如:
从自定义的输入流中反序列化
该流需要满足以下约束条件:
此外,如果该流还额外支持
read_view
函数,则支持对string_view的零拷贝优化。
示例代码如下所示:
支持可变长编码:
派生类型支持
struct_pack 同样支持序列化/反序列化派生自基类的子类,但需要额外的宏来标记派生关系并自动生成工厂函数。
然后用户就可以正常的序列化/反序列化
unique_ptr<base>
类型了。
用户同样可以序列化任意派生类然后将其反序列化为指向基类的指针。
ID冲突/哈希冲突
当两个派生类型具有完全相同的字段时,会发生ID冲突,因为struct_pack通过类型哈希来生成ID。两个相同的ID将导致struct_pack无法正确反序列化出对应的派生类。struct_pack会在编译期检查出这样的错误。
此外,任意两个不同的类型也有$2^-32$的概率发生哈希冲突,导致struct_pack无法利用哈希信息检查出类型错误。在Debug模式下,struct_pack默认带有完整类型字符串,以缓解哈希冲突。
用户可以手动给类型打标记来修复这一问题。即给该类型添加成员
constexpr static std::size_t struct_pack_id
并赋一个唯一的初值。如果不想侵入式的修改类的定义,也可以选择在同namespace下添加函数
constexpr std::size_t struct_pack_id(Type*)
。
当添加了该字段/函数后,struct_pack会在类型字符串中加上该ID,从而保证两个类型之间具有不同的类型哈希值,从而解决ID冲突/哈希冲突。
benchmark
测试方法
待序列化的对象已经预先初始化,存储序列化结果的内存已经预先分配。对每个测试用例。我们运行一百万次序列化/反序列化,对结果取平均值。
测试对象
- 含有整形、浮点型和字符串类型person对象
- 含有十几个字段包括嵌套对象的复杂对象monster
- 含有4个int32的rect对象
测试环境
Compiler: Alibaba Clang 13
CPU: (Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz)
测试结果
向前/向后兼容性
当对象增加新的字段时,怎么保证兼容新旧对象的解析呢?当用户需要添加字段时,只需要在 增加新的
struct_pack::compatible<T>
字段即可。
以person对象为例: