反序列化时通过工厂函数根据头信息(如前4字节version)选择解析器实例,头需固定长度、明确字节序,工厂仅解析头并返回对应版本解析器,不执行完整反序列化;各版本解析器继承统一抽象基类接口,子类实现字段布局与校验;须显式拒绝未知版本并抛异常,头校验推荐crc32,范围限于version和crc字段本身。

反序列化时怎么根据头信息选解析器?
靠工厂函数返回不同版本的解析器实例,而不是在反序列化逻辑里写一堆 if (version == 1) 分支。头信息(比如前 4 字节的 version 字段)必须在读取主体数据前先解析出来,否则无法决定用哪个解析器。
- 头信息建议固定长度、小端/大端明确,避免解析失败后整个流错位
- 工厂函数签名通常类似
std::unique_ptr<parser> create_parser(std::istream& is)</parser>,先 peek 或 read 头,再 new 对应版本的解析器 - 别在工厂里做完整反序列化——只负责“选人”,不负责“干活”
如何让不同版本解析器共享接口?
用抽象基类定义统一入口,比如 parse(std::istream&) 和 serialize(std::ostream&),各版本子类只重写自己关心的字段布局和校验逻辑。
- 基类不保存状态,也不管理内存布局;所有字段映射、字节序转换、默认值填充都下放到子类
- 避免虚函数调用开销敏感场景:如果性能关键,可改用 tag dispatch +
std::variant,但会增加模板膨胀 - 注意 vtable 指针大小影响:在嵌入式或内存受限环境,纯静态多态(CRTP)有时更可控
版本升级时最容易崩在哪?
不是新字段加不上,而是旧解析器遇到新头版本直接抛异常或静默跳过,导致上层认为“数据损坏”而非“版本不支持”。
- 工厂必须显式检查未知
version并 throwstd::runtime_error("unsupported version: " + std::to_string(v)) - 反序列化函数内部别用
try/catch吞掉底层std::ios_base::failure——这会掩盖读取截断等真实错误 - 测试必须覆盖边界:比如 v2 解析器读 v1 数据(兼容)、v1 解析器读 v2 数据(拒绝)、头合法但后续字节不足(IO 错误优先于版本错误)
头信息该不该加密或加 CRC?
取决于威胁模型。普通进程间通信不用;跨不可信边界(如网络接收、磁盘加载第三方插件)必须加校验,否则攻击者可伪造 version 跳转到有漏洞的旧解析路径。
立即学习“C++免费学习笔记(深入)”;
- CRC32 足够快,适合头部校验;别用 MD5/SHA —— 不是防篡改,是防意外损坏
- 加密头(如 AES-GCM)只有在需要保密版本号时才值得,多数场景纯属过度设计
- 校验范围要严格:只包
version和crc字段本身,别把后续 payload 长度也算进去,否则版本升级时校验逻辑要跟着变










