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

用 std::ostream + write() 直接写内存最简单,但只适用于 POD 类型
POD(Plain Old Data)类型——比如只有 int、double、char[32] 这类成员,没虚函数、没继承、没自定义构造/析构的 struct/class——可以直接用 reinterpret_cast<char>(&obj)</char> 读写内存。这是最快、最轻量的方式。
常见错误现象:std::bad_cast 或读出来全是乱码,往往是因为类里混了 std::string、std::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(),它不保证结尾有\0,且长度不可靠 - 数值统一用小端或大端(推荐
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) - 别在序列化函数里抛异常,改用返回
bool或std::expected<void err></void>(C++23)做错误传递
std::memcpy 和 std::bit_cast 在 C++20 后能替代部分 reinterpret_cast,但不解决逻辑层问题
std::bit_cast 比 reinterpret_cast 安全,编译期检查大小和可平凡复制性;std::memcpy 比直接指针强转更符合严格别名规则。但它们只是帮你更安全地“搬字节”,不帮你决定“哪些字节该搬、顺序怎么排、字符串怎么截”。
容易踩的坑:
- 对非 trivially copyable 类型用
std::bit_cast编译失败,但错误信息可能很绕(比如报not a literal type) - 用
memcpy复制含指针的对象,只是复制了指针值,不是深拷贝内容 - 即使所有字段都用了
std::bit_cast,如果协议没约定字段顺序或字节序,跨平台仍会错乱
真正难的从来不是怎么把内存塞进文件,而是让发送方和接收方对“这一串字节到底代表什么”有一模一样的理解。字段增减、版本升级、大小端混用、对齐差异——这些地方出问题,比语法错误更难 debug。









