c++中如何将结构体序列化_c++对象转换为二进制流方法

不安全,仅当结构体满足 std::is_trivially_copyable_v 时才可 memcpy;含虚函数、非平凡成员、std::string、指针、对齐差异或跨平台时均会出错。

直接 memcpy 结构体是否安全?

多数情况下不安全。只有当结构体满足 std::is_trivially_copyable_v 时,memcpy 才能正确序列化。常见破坏条件包括:含虚函数、非平凡构造/析构函数、含 std::string / std::vector 等非 POD 成员、有字节对齐差异(如跨平台传输)、或含指针成员(只拷贝地址,不拷贝所指数据)。

验证方式:

static_assert(std::is_trivially_copyable_v, "MyStruct is not trivially copyable");
  • 若断言失败,说明不能用 memcpy 直接转二进制流
  • 即使本地运行正常,跨编译器或跨平台(如 x86_64 ↔ ARM64)也可能因 padding、endianness 或 ABI 差异出错
  • 调试时容易误判——“看起来能读出来” ≠ “语义正确”

含 std::string 或容器的结构体怎么序列化?

必须手动序列化每个字段,不能整体 memcpy。核心思路是:先写长度,再写内容;对嵌套对象递归处理。

例如:

struct Person {
    int id;
    std::string name;
    std::vector scores;
};

std::vector serialize(const Person& p) {
    std::vector buf;
    // 写 id(4 字节)
    buf.insert(buf.end(), reinterpret_cast(&p.id), 
       

reinterpret_cast(&p.id) + sizeof(p.id)); // 写 name 长度 + 数据 uint32_t len = static_cast(p.name.size()); buf.insert(buf.end(), reinterpret_cast(&len), reinterpret_cast(&len) + sizeof(len)); buf.insert(buf.end(), p.name.begin(), p.name.end()); // 写 scores 大小和元素 len = static_cast(p.scores.size()); buf.insert(buf.end(), reinterpret_cast(&len), reinterpret_cast(&len) + sizeof(len)); for (double d : p.scores) { buf.insert(buf.end(), reinterpret_cast(&d), reinterpret_cast(&d) + sizeof(d)); } return buf; }
  • 注意 std::string::data() 不保证以 \0 结尾,所以必须显式存长度
  • 浮点数直接按位拷贝可跨平台(IEEE 754),但需统一 endianness(建议网络字节序,用 htons/htonl
  • 未处理异常(如内存分配失败)或版本兼容性(后续加字段怎么办)

用 boost::serialization 还是 cereal?

cereal 更轻量、头文件即用、无运行时开销,适合嵌入式或性能敏感场景;boost::serialization 功能全但依赖大、编译慢、二进制格式不兼容旧版。

cereal 的典型写法:

#include 
#include 
#include 

struct Person {
    int id;
    std::string name;
    std::vector scores;

    template 
    void serialize(Archive& ar) {
        ar(CEREAL_NVP(id), CEREAL_NVP(name), CEREAL_NVP(scores));
    }
};

// 序列化
std::ostringstream os;
cereal::BinaryOutputArchive oarchive(os);
oarchive(person);

std::vector bin = os.str(); // 获取二进制流
  • 必须为每个可序列化类型提供 serialize 成员函数或非成员特化
  • cereal 默认不处理多态(需额外宏),也不支持直接指针序列化(会报错)
  • 生成的二进制不是自描述的——反序列化端必须用完全相同的 struct 定义和 cereal 版本

跨平台传输时最容易忽略的三个点

哪怕结构体是 trivially copyable,跨平台仍可能失败:

  • intlong 在不同平台宽度不同(Linux x86_64 中 long 是 8 字节,Windows 是 4 字节),应改用 int32_t / uint64_t 等固定宽度类型
  • 大小端不一致:x86 是小端,ARM 可配,网络协议通常要求大端;建议所有整数字段在序列化前调用 htons / htonl / htobe64
  • 结构体 padding 差异:同一定义在 GCC 和 MSVC 下可能因默认对齐策略不同而 layout 不同;用 #pragma pack(1)[[gnu::packed]] 强制紧凑,但要确认目标平台支持未对齐访问(否则 SIGBUS)

真正可靠的序列化,从来不是“把内存 dump 出来”,而是明确定义字段顺序、编码规则和边界行为。哪怕只是两个进程间通信,也值得花十分钟写个 serialize() / deserialize() 对。