
序列化时怎么加版本号字段
版本号必须是序列化数据的固定前置字段,不能靠运行时判断或额外配置。否则旧版本程序读新格式会直接解析失败,连“版本不匹配”的提示都来不及抛出。
推荐在每个可序列化的类里显式定义 uint32_t version 成员,并确保它永远排在序列化字节流最开头(比如用 std::vector<uint8_t></uint8_t> 手动拼接,或用 boost::serialization 的 version 宏)。
- 用
boost::serialization时,在类定义后加BOOST_CLASS_VERSION(MyClass, 2),它会自动在存档头部写入版本值 - 手写二进制序列化时,先写
version再写其他字段,读取时第一件事就是读这个version值并校验范围 - 别把版本号藏在 JSON key 名里(比如
"data_v2"),这会让解析逻辑耦合、难以统一处理
读取时如何安全跳过新增字段
旧版程序不能因为遇到不认识的字段就崩溃——它得能跳过未知字段继续读后面已知的部分,否则一次小迭代就会导致全量数据不可读。
关键不是“支持所有未来字段”,而是“明确知道哪些字段可跳过”。所以协议设计阶段就要约定字段标识方式:比如每个字段前加 2 字节 tag + 4 字节 length,tag 为 0 表示保留字段,tag > 100 的字段默认可跳过。
立即学习“C++免费学习笔记(深入)”;
- 读取循环中检测到未知
tag,就用length偏移跳过对应字节,不解析也不报错 - JSON 场景下,用
nlohmann::json的at()而非operator[],避免访问不存在 key 时抛异常;对可选字段用value()提供默认值 - 别依赖字段顺序做兼容——有些序列化库(如 Protobuf)允许重排字段,但手写二进制流若没 tag-length 就没法跳过
反序列化失败时该返回什么错误类型
不能只抛 std::runtime_error 或打印日志了事。调用方需要区分是数据损坏、版本越界,还是字段缺失——每种情况的恢复策略完全不同。
建议定义枚举错误码,让上层决定是否降级加载、丢弃数据或告警人工介入。
enum class DeserializeError { kInvalidHeader, kVersionTooNew, kVersionTooOld, kMissingRequiredField, kCorruptedData }- 比如
kVersionTooOld可触发自动迁移(调用 v1 → v2 转换函数),而kCorruptedData应直接拒绝加载 - Protobuf 的
ParseFromString()返回 bool,但不告诉你为什么失败;用GetUnknownFields()配合自定义解析器才能拿到足够上下文
迁移函数写在哪、怎么触发
迁移逻辑不能散落在各处,也不能靠运行时 if-else 判断版本号——那会迅速变成维护噩梦。必须集中管理、显式注册、按需调用。
一个轻量方案:每个类配一个静态 migrate() 函数,接收旧版本数据指针和目标版本号,返回新版本对象。加载时查表调用对应链式迁移(v1→v2→v3)。
- 用
std::map<uint32_t std::function void size_t>></uint32_t>存储迁移函数,key 是源版本 - 反序列化发现
version == 1且当前支持v3,就先调migrate_v1_to_v2,再调migrate_v2_to_v3 - 迁移函数内部只处理字段映射和默认值填充,不涉及业务逻辑——业务逻辑应在反序列化完成后由调用方决定
版本字段位置、跳过逻辑的边界条件、迁移函数的注册时机——这三个地方出错,基本就等于整个兼容性机制失效。它们不难写,但极容易被当成“细节”一带而过。










