如何实现支持堆数据的Boost通用二进制序列化与反序列化函数?
Boost序列化通用保存/加载函数修正方案
问题描述
我正在使用Boost库,希望将序列化后的对象保存到二进制文件并从中读取。现有多个自定义对象(如MyCustomData和MyOtherCustomData),想要实现通用的saveData保存函数和loadData读取函数,通过void指针传递数据,但当前saveData无法正确序列化堆上的数据,求修正方案。
原示例代码:
#include <iostream> #include <fstream> #include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> #include <boost/serialization/binary_object.hpp> // some data struct MyInfo { int x; char z[10]; }; class MyCustomData { protected: MyInfo* _data; std::string _str1; public: MyCustomData(const std::string& s1, int x, const char* z) : _str1(s1) { _data = new MyInfo(); _data->x = x; strcpy_s(_data->z, sizeof(_data->z), z); } virtual ~MyCustomData() { delete _data; } virtual std::string print() { std::string res(_str1); res += ", data: " + std::to_string(_data->x) + ", "; res.append(_data->z); return res; } }; class MyOtherCustomData : public MyCustomData { std::string _str2; public: MyOtherCustomData(const std::string& s1, const std::string& s2, int x, const char* z) : MyCustomData(s2, x, z) , _str2(s1) { } ~MyOtherCustomData() { } std::string print() override { std::string res(_str2); res += ", " + _str1; res += ", data: " + std::to_string(_data->x) + ", "; res.append(_data->z); return res; } }; // save serialized data to file bool saveData(std::string path, void* data, std::size_t datasize, std::string& err) { bool res = false; std::ofstream fout; fout.open(path, std::ios::trunc); if (!fout.is_open()) { err = "can't open file " + path; } else { boost::archive::binary_oarchive archive(fout); archive << boost::serialization::make_binary_object(data, datasize); res = true; } fout.close(); return res; } // load serialized data from file bool loadData(std::string path, void* data, std::size_t datasize, std::string& err) { bool res = false; std::ifstream fin; fin.open(path, std::ios::in | std::ios::out); if (!fin.is_open()) { err = "can't open file " + path; } else { // How to make correct read??? auto obj = boost::serialization::make_binary_object(data, datasize); boost::archive::binary_iarchive archive(fin); archive >> obj; res = true; } fin.close(); return res; } int main() { std::string err; // SAVE { MyCustomData obj1("my info 1", 1, "info1"); MyOtherCustomData obj2("other", "my info 2", 3, "info2"); // save to file if (!saveData("data1.dat", &obj1, sizeof(MyCustomData), err)) std::cout << "save error: " << err.c_str() << std::endl; else std::cout << "saved: " << obj1.print().c_str() << std::endl; // save to file if (!saveData("data2.dat", &obj2, sizeof(MyOtherCustomData), err)) std::cout << "save error: " << err.c_str() << std::endl; else std::cout << "saved: " << obj2.print().c_str() << std::endl; } // READ { MyCustomData obj3("temp 1", 10, "temp1"); MyOtherCustomData obj4("other temp", "temp 2", 20, "temp2"); // read from file if (!loadData("data1.dat", &obj3, sizeof(MyCustomData), err)) std::cout << "load error: " << err.c_str() << std::endl; else std::cout << "loaded: " << obj3.print().c_str() << std::endl; // read from file if (!loadData("data2.dat", &obj4, sizeof(MyOtherCustomData), err)) std::cout << "load error: " << err.c_str() << std::endl; else std::cout << "loaded: " << obj4.print().c_str() << std::endl; } }
问题根源
原代码使用boost::serialization::make_binary_object直接拷贝对象内存快照,这种方式仅适用于无堆分配、无复杂成员、无继承关系的简单POD类型。对于包含堆指针(如_data)、std::string等复杂类型,以及存在继承关系的类,该方法无法递归序列化内部数据,也无法处理多态类型信息,导致序列化不完整。
修正步骤
1. 为自定义类型添加Boost序列化支持
Boost Serialization需要每个可序列化的类提供serialize函数,用于定义如何序列化类的成员:
- 为
MyInfo结构体添加序列化函数,直接序列化其成员 - 为
MyCustomData添加序列化函数,处理指针_data(Boost会自动处理堆内存的序列化)和_str1,同时声明支持继承 - 为
MyOtherCustomData添加序列化函数,先序列化基类部分,再处理子类新增的_str2
2. 重写通用的save/load函数为模板函数
放弃void*传递,改用模板函数保留类型信息,让Boost归档可以正确识别对象类型,完成完整序列化。同时修正文件打开模式:必须添加std::ios::binary,避免文本模式下的字节转换问题。
修改后的完整代码
#include <iostream> #include <fstream> #include <string> #include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> #include <boost/serialization/serialization.hpp> #include <boost/serialization/string.hpp> #include <boost/serialization/export.hpp> // some data struct MyInfo { int x; char z[10]; // 序列化MyInfo template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar & x; ar & z; } }; class MyCustomData { protected: MyInfo* _data; std::string _str1; // 序列化函数(protected,让子类可以调用) template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar & _data; // Boost自动处理指针指向的堆内存 ar & _str1; } // 必须声明默认构造函数,Boost反序列化需要 MyCustomData() : _data(nullptr) {} public: MyCustomData(const std::string& s1, int x, const char* z) : _str1(s1) { _data = new MyInfo(); _data->x = x; strcpy_s(_data->z, sizeof(_data->z), z); } virtual ~MyCustomData() { delete _data; } virtual std::string print() { std::string res(_str1); res += ", data: " + std::to_string(_data->x) + ", "; res.append(_data->z); return res; } // 声明支持序列化继承 friend class boost::serialization::access; }; // 导出子类,支持多态序列化(如果需要序列化基类指针指向子类对象的场景) BOOST_CLASS_EXPORT_KEY(MyOtherCustomData) class MyOtherCustomData : public MyCustomData { std::string _str2; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar & boost::serialization::base_object<MyCustomData>(*this); // 序列化基类部分 ar & _str2; // 序列化子类新增成员 } // 默认构造函数 MyOtherCustomData() : MyCustomData() {} public: MyOtherCustomData(const std::string& s1, const std::string& s2, int x, const char* z) : MyCustomData(s2, x, z) , _str2(s1) { } ~MyOtherCustomData() { } std::string print() override { std::string res(_str2); res += ", " + _str1; res += ", data: " + std::to_string(_data->x) + ", "; res.append(_data->z); return res; } friend class boost::serialization::access; }; // 通用保存函数(模板版本) template<typename T> bool saveData(const std::string& path, const T& data, std::string& err) { try { std::ofstream fout(path, std::ios::trunc | std::ios::binary); if (!fout.is_open()) { err = "无法打开文件: " + path; return false; } boost::archive::binary_oarchive archive(fout); archive << data; return true; } catch (const std::exception& e) { err = "保存失败: " + std::string(e.what()); return false; } } // 通用加载函数(模板版本) template<typename T> bool loadData(const std::string& path, T& data, std::string& err) { try { std::ifstream fin(path, std::ios::in | std::ios::binary); if (!fin.is_open()) { err = "无法打开文件: " + path; return false; } boost::archive::binary_iarchive archive(fin); archive >> data; return true; } catch (const std::exception& e) { err = "加载失败: " + std::string(e.what()); return false; } } int main() { std::string err; // SAVE { MyCustomData obj1("my info 1", 1, "info1"); MyOtherCustomData obj2("other", "my info 2", 3, "info2"); if (!saveData("data1.dat", obj1, err)) std::cout << "保存错误: " << err << std::endl; else std::cout << "已保存: " << obj1.print() << std::endl; if (!saveData("data2.dat", obj2, err)) std::cout << "保存错误: " << err << std::endl; else std::cout << "已保存: " << obj2.print() << std::endl; } // READ { // 注意:反序列化时对象会被重新构造,初始值会被覆盖 MyCustomData obj3("temp 1", 10, "temp1"); MyOtherCustomData obj4("other temp", "temp 2", 20, "temp2"); if (!loadData("data1.dat", obj3, err)) std::cout << "加载错误: " << err << std::endl; else std::cout << "已加载: " << obj3.print() << std::endl; if (!loadData("data2.dat", obj4, err)) std::cout << "加载错误: " << err << std::endl; else std::cout << "已加载: " << obj4.print() << std::endl; } }
关键说明
- 默认构造函数:Boost反序列化需要类有默认构造函数(可以是private,只要让
boost::serialization::access成为友元) - 指针序列化:Boost会自动处理
MyInfo* _data的序列化,包括分配内存、写入/读取堆上的数据 - 多态支持:如果需要序列化基类指针指向子类对象的场景,需要使用
BOOST_CLASS_EXPORT_KEY或BOOST_CLASS_EXPORT宏导出子类 - 文件模式:必须添加
std::ios::binary,避免文本模式下换行符等字节被转换,导致序列化数据损坏 - 模板函数:模板保留了对象的类型信息,让Boost可以调用对应的
serialize函数,完成完整的序列化
内容的提问来源于stack exchange,提问作者Pasha
相关产品推荐
相关产品推荐

