C++如何实现对象序列化为二进制?(自定义协议)

6次阅读

pod类型可直接用std::ostream::write()内存序列化,但含std::String/vector或虚函数的类必须手动序列化字段;需校验trivially_copyable、统一字节序、用长度前缀处理字符串,并避免reinterpret_cast误用。

C++如何实现对象序列化为二进制?(自定义协议)

std::ostream + write() 直接写内存最简单,但只适用于 POD 类型

POD(Plain Old Data)类型——比如只有 intdoublechar[32] 这类成员,没虚函数、没继承、没自定义构造/析构的 Struct/class——可以直接用 reinterpret_cast<char>(&obj)</char> 读写内存。这是最快、最轻量的方式。

常见错误现象:std::bad_cast 或读出来全是乱码,往往是因为类里混了 std::stringstd::vector指针;这些成员不是内存连续的,直接 write() 只存了指针值,反序列化时完全失效。

实操建议:

  • 先用 std::is_pod_v<t></t>std::is_standard_layout_v<t></t> + std::is_trivially_copyable_v<t></t> 静态断言确认类型安全
  • 写入时用 os.write(reinterpret_cast<const char>(&obj), sizeof(obj))</const>
  • 读取时确保目标对象未初始化(或用 placement new),再 is.read(...)

std::string / std::vector 的类必须手动序列化字段

这类容器内部是分配的,二进制协议里不能只存指针。你得自己规定字段顺序、长度编码和边界对齐方式——这就是“自定义协议”的实质:你说了算,但每一步都得亲手控制。

立即学习C++免费学习笔记(深入)”;

使用场景:网络传输、本地缓存、跨进程共享数据。重点不是“能不能”,而是“字段怎么排、字符串怎么定长/变长、要不要校验”。

实操建议:

  • 字符串优先用“长度前缀 + 字节流”:先写 uint32_t len,再写 data.data()len 字节
  • 避免直接写 std::string::c_str(),它不保证结尾有 ,且长度不可靠
  • 数值统一用小端或大端(推荐 htole32() / le32toh() 显式转换),别依赖平台默认
  • 结构体字段之间加 static_assert(offsetof(MyMsg, field2) == offsetof(MyMsg, field1) + sizeof(field1)) 防意外 padding

遇到继承或虚函数?放弃内存直写,改用虚函数接口 + 成员逐个序列化

只要类有虚表(哪怕只有一个虚析构),sizeof 就不等于实际可序列化字段总和,reinterpret_cast 必崩。这时候必须把序列化逻辑下沉到每个类内部。

性能影响明显:多了虚函数调用、多次 write() 系统调用开销、无法单次 memcpy。但换来的是健壮性和扩展性——加个新字段,只改本类的 serialize() 即可。

实操建议:

  • 基类定义纯虚函数virtual void serialize(std::ostream& os) const = 0
  • 派生类里按字段顺序调用 os.write(...),字符串照旧用长度前缀
  • 反序列化时,先读类型标识(比如 1 字节 enum),再用工厂函数构造对应子类并调用 deserialize(is)
  • 别在序列化函数里抛异常,改用返回 boolstd::expected<void err></void>c++23)做错误传递

std::memcpystd::bit_cast 在 C++20 后能替代部分 reinterpret_cast,但不解决逻辑层问题

std::bit_castreinterpret_cast 安全,编译期检查大小和可平凡复制性;std::memcpy 比直接指针强转更符合严格别名规则。但它们只是帮你更安全地“搬字节”,不帮你决定“哪些字节该搬、顺序怎么排、字符串怎么截”。

容易踩的坑:

  • 对非 trivially copyable 类型用 std::bit_cast 编译失败,但错误信息可能很绕(比如报 not a literal type
  • memcpy 复制含指针的对象,只是复制了指针值,不是深拷贝内容
  • 即使所有字段都用了 std::bit_cast,如果协议没约定字段顺序或字节序,跨平台仍会错乱

真正难的从来不是怎么把内存塞进文件,而是让发送方和接收方对“这一串字节到底代表什么”有一模一样的理解。字段增减、版本升级、大小端混用、对齐差异——这些地方出问题,比语法错误更难 debug。

text=ZqhQzanResources