发布于2026-07-18 阅读(0)
扫一扫,手机访问
C++ 对象持久化,这事儿说难不难,说简单也不简单。很多开发者第一次遇到“把 class 存到文件”的需求时,往往先想到的是直接写二进制流——这确实是最直接的方式,但里面的坑,一个比一个深。
先看最直接的做法:用 std::ofstream 二进制写入。如果你的类没有指针、虚函数、动态分配内存,所有成员都是基本类型或 std::array 这样的固定大小容器,那逐字段写入确实没问题。比如一个简单的 Person 结构体:
class Person {
public:
int id;
char name[32];
double salary;
};
// 写入
std::ofstream file("person.bin", std::ios::binary);
Person p{101, "Alice", 8500.0};
file.write(reinterpret_cast(&p), sizeof(p));
file.close();
看着挺干净,但有几个细节必须注意:sizeof(p) 包含结构体填充字节,不同编译器、不同平台的对齐方式可能不一样,这就意味着跨平台读取时可能出问题。另外,char name[32] 是安全的,因为它在栈上固定分配。但如果是 std::string,这么写就完蛋了——它内部存的是指针,直接写进去只保存了地址值,等下次读取时,那个地址指向的内容早就没了,程序崩得妥妥的。
标准容器不是 POD 类型,内存布局不连续,所以不能直接写。正确的做法是:先写长度,再写内容。比如:
class Data {
public:
std::string msg;
std::vector nums;
};
void sa ve(const Data& d, std::ofstream& out) {
size_t len = d.msg.size();
out.write(reinterpret_cast(&len), sizeof(len));
out.write(d.msg.data(), len); // 不带 '\0'
len = d.nums.size();
out.write(reinterpret_cast(&len), sizeof(len));
if (!d.nums.empty()) {
out.write(reinterpret_cast(d.nums.data()), len * sizeof(int));
}
}
\0,读取时按长度申请空间再读std::vector 的 data() 可能为 nullptr,要判空避免 write(nullptr, 0) 行为未定义size_t 大小(32/64 位不一致),建议统一用 uint64_t手动操作虽然可控,但代码量不小,而且容易遗漏字段。如果觉得这么搞太麻烦,可以考虑 Boost 的方案。
Boost 提供反射式序列化,支持指针、继承、STL 容器,但要求类有默认构造函数(反序列化时先构造再赋值)。用起来是这样的:
#include#include class Config { public: std::string host; int port; Config() = default; // 必须存在 private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int) { ar & host & port; } }; // 写入 std::ofstream ofs("config.bin", std::ios::binary); boost::archive::binary_oarchive oa(ofs); oa << config;
const 成员、引用成员;私有成员需设为 friend有意思的是,Boost 虽然方便,但它的二进制归档格式在版本之间并不稳定。如果你需要长期存储数据,或者跨语言交互,这条路并不稳。
话说回来,如果对象要存好几年,或者需要跟 Python、Go 等其他语言交互,手写或者 Boost 都不够稳健。这时候,Protocol Buffers 或 msgpack 才是正解。拿 Protobuf 举例,先定义 .proto 文件:
syntax = "proto3";
message Person {
int32 id = 1;
string name = 2;
double salary = 3;
}
生成 C++ 类后,序列化只需两行:
Person p;
p.set_id(101);
p.set_name("Alice");
p.set_salary(8500.0);
std::ofstream out("person.pb", std::ios::binary);
p.SerializeToOstream(&out); // 自动处理变长整数、字符串长度、嵌套等
其实,真正麻烦的从来不是“怎么写进去”,而是“以后还能不能读出来”。字段改名、加默认值、换编译器、升级标准库,都可能让昨天还能读的文件今天直接罢工。所以,选方案之前,先想清楚这个对象要存多久、谁会来读它——这才是关键所在。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8