google::protobuf提供了一种基于tag号的版本兼容的序列化机制,并通过代码生成技术,支持了idl式的序列化接口定义;不过有些情况下,仅使用idl定义模式支持的序列化无法完全满足需求
这些场景下用到的自定义结构就无法直接借助protobuf来完成序列化。往往需要额外定义一套信息量对等的message,并额外增加手动赋值等操作开销。为了简便支持自定义结构的序列化,以及实现基于tag的版本兼容机制,制作了一套声明式的宏函数,来支持这些自定义结构的序列化
#include <babylon/serialization.h>
using ::babylon::Serialization;
using ::babylon::SerializeTraits;
// 序列化和反序列化统一通过Serialization静态成员函数提供
// value需要实现序列化协议babylon::SerializeTraits
// 返回success用于判定操作成功
// 判断T是否可序列化
// 不可序列化时,也可以调用相关函数,但是默认都会返回失败
if (SerializeTraits<T>::SERIALIZABLE) {
}
// string版本
std::string s;
success = Serialization::serialize_to_string(value, s);
success = Serialization::parse_from_string(s, value);
// coded stream版本
google::protobuf::io::CodedOutputStream cos;
google::protobuf::io::CodedInputStream cis;
success = Serialization::serialize_to_coded_stream(value, cos);
success = Serialization::parse_from_coded_stream(cis, value);
// 输出debug stirng,string版本
std::string s;
success = Serialization::print_to_string(value, s);
// 输出debug stirng,stream版本
google::protobuf::io::ZeroCopyOutputStream os;
success = Serialization::print_to_stream(value, os);
// 默认对于如下value类型默认支持
// 1. proto idl生成类,即google::protobuf::MessageLite子类
// 2. 基础类型,即bool, int*_t, uint*_t, float, double, enum, enum class, std::string
// 3. 基础容器:std::vector<T>, std::unique_ptr<T>
#include <babylon/serialization.h>
// 任意自定义类型
class SomeType {
public:
... // 常规方法定义
// 成员不需要公开访问权限
private:
int32_t a;
float b;
std::vector<int64_t> c;
// SomeOtherType需要通过宏声明等方式支持序列化
SomeOtherType d;
// 包装一层
std::unique_ptr<SomeOtherType> e;
// proto idl定义类天然可序列化
SomeMessage f;
std::unique_ptr<SomeMesssage> g;
...
// 声明后SomeType即可进行序列化
// 只有列出的成员会实际参与序列化过程
BABYLON_SERIALIZABLE(a, b, c, d, e, f, g, ...);
};
class SomeType {
public:
...
private:
...
// 声明后SomeType即可进行跨版本序列化
// 只有列出的成员会实际参与序列化过程
// 每个成员需要对应唯一的tag号
// 在迭代过程中,成员可以增删,但是要tag号不会重复使用
BABYLON_COMPATIBLE((a, 1)(b, 2)(c, 3)...);
};
#include <babylon/serialization.h>
// 一个可序列化的基类
class BaseType {
public:
...
private:
...
// 正常定义为可序列化
BABYLON_SERIALIZABLE(...);
// 或者可版本兼容
BABYLON_COMPATIBLE(...);
};
class SubType : public BaseType {
public:
...
private:
...
// 可序列化级联,BaseType会像第一个成员一样序列化
BABYLON_SERIALIZABLE_WITH_BASE(BaseType, ...);
// 版本兼容级联,需要为BaseType指定独立tag
// 会像一个具有BaseType类型的成员一样序列化
BABYLON_COMPATIBLE_WITH_BASE((BaseType, 1), ...);
};
#include <babylon/serialization.h>
// 对于无法简单进行宏声明改造的复杂类型
// 可以通过直接实现相关接口的方式来支持序列化
// 基础实现
class SomeType {
public:
// 可以不实现此函数
// 实现后返回true表示序列化方式可版本兼容,相当于BABYLON_COMPATIBLE声明
// 返回false和不实现含义相同,默认相当于BABYLON_SERIALIZABLE声明
static constexpr bool serialize_compatible() {
return ...;
}
void serialize(CodedOutputStream& os) const {
... // 将this序列化并写入os
}
bool deserialize(CodedInputStream& is) {
... // 从is读入数据,并重建到this
return ...; // 正常完成返回true,解析错误等返回false
}
size_t caculate_serialized_size() const noexcept {
return ...; // 计算并返回『序列化后』的bytes数
}
... // 其他函数和成员定义
};
// size计算复杂时需要通过缓存等机制加速
// 可以通过额外函数进行声明和支持
class SomeType {
public:
// 声明计算size的复杂度,分为TRIVIAL/SIMPLE/COMPLEX三个级别
// 代表的编译期可计算,O(1)可计算和O(n)可计算
// 注意,如果设计了后面的缓存机制,那么这里需要声明的是『使用缓存』时的复杂度
static constexpr int serialized_size_complexity() {
return SerializationHelper::SERIALIZED_SIZE_COMPLEXITY_SIMPLE;
}
// 实现用于加速size计算的缓存机制
// 框架会保证在调用serialize函数前,一定调用过caculate_serialized_size
// 这样实际进行serialize内部可以改为调用serialized_size_cached
// 并通过缓存上一次caculate_serialized_size的结果来加速
// 这也是protobuf::Message采用的加速手段
size_t serialized_size_cached() const noexcept {
return ...;
}
...
};
////////////////////////////////////
// test.proto 假设有如下一个proto定义
enum TestEnum {
};
message TestMessage {
optional bool b = 1;
optional int32 i32 = 4;
optional int64 i64 = 5;
optional uint32 u32 = 8;
optional uint64 u64 = 9;
optional float f = 16;
optional double d = 17;
optional TestEnum e = 18;
optional string s = 19;
optional bytes by = 20;
optional TestMessage m = 21;
repeated bool rpb = 44 [packed = true];
repeated int32 rpi32 = 47 [packed = true];
repeated int64 rpi64 = 48 [packed = true];
repeated uint32 rpu32 = 51 [packed = true];
repeated uint64 rpu64 = 52 [packed = true];
repeated float rpf = 59 [packed = true];
repeated double rpd = 60 [packed = true];
repeated TestEnum rpe = 61 [packed = true];
};
////////////////////////////////////
///////////////////////////////////
// test.cc 假设有如下一个类定义
#include <babylon/serialization.h>
enum TestEnum {
};
struct TestSubObject {
bool _b;
int32_t _i32;
int64_t _i64;
uint32_t _u32;
uint64_t _u64;
float _f;
double _d;
TestEnum _e;
::std::string _s;
::std::string _by;
::std::vector<::std::string> _rs;
::std::vector<::std::string> _rby;
::std::vector<bool> _rpb;
::std::vector<int32_t> _rpi32;
::std::vector<int64_t> _rpi64;
::std::vector<uint32_t> _rpu32;
::std::vector<uint64_t> _rpu64;
::std::vector<float> _rpf;
::std::vector<double> _rpd;
::std::vector<TestEnum> _rpe;
BABYLON_COMPATIBLE(
(_b, 1)(_i32, 4)(_i64, 5)(_u32, 8)(_u64, 9)
(_f, 16)(_d, 17)(_e, 18)(_s, 19)(_by, 20)
(_rs, 41)(_rby, 42)
(_rpb, 44)(_rpi32, 47)(_rpi64, 48)(_rpu32, 51)(_rpu64, 52)
(_rpf, 59)(_rpd, 60)(_rpe, 61)
);
};
struct TestObject {
bool _b;
int32_t _i32;
int64_t _i64;
uint32_t _u32;
uint64_t _u64;
float _f;
double _d;
TestEnum _e;
::std::string _s;
::std::string _by;
TestSubObject _m;
::std::vector<::std::string> _rs;
::std::vector<::std::string> _rby;
::std::vector<TestSubObject> _rm;
::std::vector<bool> _rpb;
::std::vector<int32_t> _rpi32;
::std::vector<int64_t> _rpi64;
::std::vector<uint32_t> _rpu32;
::std::vector<uint64_t> _rpu64;
::std::vector<float> _rpf;
::std::vector<double> _rpd;
::std::vector<TestEnum> _rpe;
BABYLON_COMPATIBLE(
(_b, 1)(_i32, 4)(_i64, 5)(_u32, 8)(_u64, 9)
(_f, 16)(_d, 17)(_e, 18)(_s, 19)(_by, 20)
(_m, 21)(_rs, 41)(_rby, 42)(_rm, 43)
(_rpb, 44)(_rpi32, 47)(_rpi64, 48)(_rpu32, 51)(_rpu64, 52)
(_rpf, 59)(_rpd, 60)(_rpe, 61)
);
};
// struct -> message正向可兼容
TestObject s;
Serialization::serialize_to_string(s, str);
TestMessage m;
Serialization::parse_from_string(str, m);
// message -> struct反向可兼容
TestObject m;
Serialization::serialize_to_string(m, str);
TestMessage s;
Serialization::parse_from_string(str, s);
///////////////////////////////////