C++ boost::archive 详解:序列化库的全面指南
- 一、C++ boost::archive
- 引言
- 1、 boost::archive 核心概念
- 1.1 、什么是 Archive?
- 1.2 、序列化函数:`serialize()`
- 2、快速入门:一个简单的例子
- 3、 不同存档格式详解
- 3.1、 文本存档 (`text_oarchive` / `text_iarchive`)
- 3.2、 二进制存档 (`binary_oarchive` / `binary_iarchive`)
- 3.3、 XML 存档 (`xml_oarchive` / `xml_iarchive`)
- 4、序列化复杂数据类型
- 4.1、 标准库容器 (`std::vector`, `std::map`, 等)
- 4.2 、继承和多态(指针序列化)
- 4.3 、版本控制
- 5、 高级技巧与最佳实践
- 5.1、 拆分 `save` 和 `load`
- 5.2、 序列化到内存缓冲区
- 5.3、性能优化
- 6、常见问题与陷阱
- 7、总结
- 二、使用步骤
- 1、下载库
- 2、Visual Studio新建工程
- 3、将源码解压到工程目录下
- 4、添加路径
- 5、测试代码
一、C++ boost::archive
引言
在 C++ 开发中,数据持久化、网络传输和进程间通信常常需要将复杂的内存对象转换为可以存储或传输的字节流,这个过程就是序列化(Serialization)。反之,从字节流重建内存对象的过程称为反序列化(Deserialization)。手动实现这些功能不仅繁琐,而且容易出错,尤其是在处理继承、指针和复杂数据结构时。
Boost C++ 库提供了一个强大、灵活且高效的序列化解决方案:boost::archive。作为 Boost.Serialization 库的核心组件,boost::archive定义了数据流进出的抽象接口,支持多种存档格式(如二进制、文本、XML),并能够与标准容器、智能指针以及用户自定义类型无缝协作。
1、 boost::archive 核心概念
1.1 、什么是 Archive?
在 Boost.Serialization 的语境中,Archive(存档)是一个扮演“中介”角色的类。它负责在序列化时,将 C++ 对象的状态(数据成员)输出到一个流(如文件、内存缓冲区、网络套接字);在反序列化时,从一个流中读取数据并重建对象。
boost::archive本身是一个模板类和一系列具体实现的基类。我们通常使用的是它的派生类,例如:
boost::archive::text_oarchive:用于输出(序列化)到文本格式。boost::archive::text_iarchive:用于从文本格式输入(反序列化)。boost::archive::binary_oarchive/binary_iarchive:用于二进制格式。boost::archive::xml_oarchive/xml_iarchive:用于 XML 格式。
1.2 、序列化函数:serialize()
要使一个类可序列化,你需要为其定义一个serialize成员函数或一个独立的serialize自由函数。这个函数是boost::archive与你的类进行“对话”的桥梁。
serialize函数通常接受两个参数:
- 一个 Archive 类型的引用。
- 一个版本号(
unsigned int),用于处理类的版本演化。
在函数体内,你使用&操作符(由 Archive 类重载)来指定需要序列化的成员变量。这个操作符在序列化(输出)和反序列化(输入)时的行为是智能的。
2、快速入门:一个简单的例子
让我们从一个最简单的例子开始,序列化一个包含基本数据类型的结构体。
#include<fstream>#include<iostream>#include<boost/archive/text_oarchive.hpp>#include<boost/archive/text_iarchive.hpp>// 1. 包含序列化支持的头文件#include<boost/serialization/serialization.hpp>// 2. 定义可序列化的类classPerson{public:Person()=default;// 反序列化需要默认构造函数Person(std::string n,inta):name(n),age(a){}// 3. 声明序列化函数为友元,使其能访问私有成员friendclassboost::serialization::access;// 4. 定义序列化函数模板template<classArchive>voidserialize(Archive&ar,constunsignedintversion){ar&name;// 使用 & 操作符序列化每个成员ar&age;}voidprint()const{std::cout<<"Name: "<<name<<", Age: "<<age<<std::endl;}private:std::string name;intage;};intmain(){// 创建对象Personalice("Alice",30);Person bob;std::cout<<"Before serialization:"<<std::endl;alice.print();// --- 序列化到文件 ---{std::ofstreamofs("person.txt");// 创建文本输出存档,并关联到文件流boost::archive::text_oarchiveoa(ofs);// 使用 << 操作符将对象写入存档oa<<alice;}// 作用域结束,存档和文件流自动刷新并关闭// --- 从文件反序列化 ---{std::ifstreamifs("person.txt");boost::archive::text_iarchiveia(ifs);// 使用 >> 操作符从存档读取对象ia>>bob;}std::cout<<"\nAfter deserialization:"<<std::endl;bob.print();// 输出: Name: Alice, Age: 30return0;}代码解析:
- 包含头文件:
boost/archive/text_oarchive.hpp和.../text_iarchive.hpp分别用于文本格式的输出和输入存档。boost/serialization/serialization.hpp提供了基础支持。 - 使类可序列化:
- 将
boost::serialization::access声明为友元类,这样serialize函数(即使是模板)就能访问类的私有成员。 - 定义模板函数
serialize(Archive & ar, ...)。函数体内的ar & member;语句是序列化的核心。对于输出存档(oarchive),它写入数据;对于输入存档(iarchive),它读取数据。
- 将
- 使用存档:
- 创建一个文件流(
ofstream/ifstream)。 - 实例化一个存档对象(如
text_oarchive),并将文件流作为构造参数传入。 - 使用
<<操作符将对象序列化到存档,或使用>>操作符从存档反序列化对象。
- 创建一个文件流(
运行此程序后,会生成一个person.txt文件,其内容类似于:
22 serialization::archive 19 0 0 5 Alice 30这些数字是 Boost.Serialization 库的内部格式信息。
3、 不同存档格式详解
boost::archive支持多种格式,各有优劣,适用于不同场景。
3.1、 文本存档 (text_oarchive/text_iarchive)
- 特点:生成人类可读(勉强可读)的文本文件。便于调试,因为你可以用文本编辑器查看序列化后的数据。
- 缺点:文件体积较大,序列化/反序列化速度较慢。
- 适用场景:配置存储、需要人工检查数据的场景、跨平台且对大小不敏感的数据交换。
3.2、 二进制存档 (binary_oarchive/binary_iarchive)
- 特点:生成紧凑的二进制文件。体积小,速度快。
- 缺点:文件不可读,且对平台字节序(Endianness)敏感。在不同字节序的机器间传输可能需要额外处理。
- 适用场景:高性能要求的本地存储、进程间通信、网络传输(配合字节序处理)。
#include<boost/archive/binary_oarchive.hpp>#include<boost/archive/binary_iarchive.hpp>// 用法与文本存档完全相同,只需替换类型名std::ofstreamofs("data.bin",std::ios::binary);boost::archive::binary_oarchiveoa(ofs);oa<<myObject;3.3、 XML 存档 (xml_oarchive/xml_iarchive)
- 特点:生成标准的 XML 文件。可读性最好,易于被其他语言(如 Python, Java)或工具解析。
- 缺点:文件体积最大,速度最慢。
- 适用场景:需要与其他系统交互、作为配置文件、需要人工编辑和验证的数据。
#include<boost/archive/xml_oarchive.hpp>#include<boost/archive/xml_iarchive.hpp>// XML 存档需要为顶层对象指定一个名称std::ofstreamofs("data.xml");boost::archive::xml_oarchiveoa(ofs);oa&boost::serialization::make_nvp("PersonObject",alice);// 使用 make_nvp 命名生成的data.xml文件会包含清晰的标签结构。
4、序列化复杂数据类型
4.1、 标准库容器 (std::vector,std::map, 等)
Boost.Serialization 已经为大多数 STL 容器提供了开箱即用的支持,只需包含对应的头文件。
#include<boost/serialization/vector.hpp>#include<boost/serialization/map.hpp>#include<boost/serialization/string.hpp>classCompany{std::vector<Person>employees;std::map<int,std::string>projects;// ...template<classArchive>voidserialize(Archive&ar,constunsignedintversion){ar&employees;ar&projects;}};4.2 、继承和多态(指针序列化)
序列化派生类对象并通过基类指针进行恢复是 Boost.Serialization 的强大功能之一。这需要:
- 使用
BOOST_CLASS_EXPORT宏为每个可多态序列化的派生类注册一个唯一的标识符。 - 在序列化和反序列化代码中,使用
boost::serialization::base_object<>来正确序列化基类部分。 - 存档必须通过基类指针或引用来操作对象。
#include<boost/serialization/export.hpp>classShape{public:virtual~Shape()=default;virtualvoiddraw()const=0;private:friendclassboost::serialization::access;template<classArchive>voidserialize(Archive&ar,constunsignedintversion){// 基类可能有一些数据成员}};classCircle:publicShape{public:Circle(doubler=0.0):radius(r){}voiddraw()constoverride{std::cout<<"Circle r="<<radius<<std::endl;}private:doubleradius;friendclassboost::serialization::access;template<classArchive>voidserialize(Archive&ar,constunsignedintversion){// 首先序列化基类部分ar&boost::serialization::base_object<Shape>(*this);ar&radius;}};// 关键:注册派生类BOOST_CLASS_EXPORT(Circle)// 使用智能指针序列化#include<memory>#include<boost/serialization/shared_ptr.hpp>intmain(){std::shared_ptr<Shape>shape=std::make_shared<Circle>(5.0);std::shared_ptr<Shape>loaded_shape;{std::ofstreamofs("shape.bin");boost::archive::binary_oarchiveoa(ofs);oa<<shape;// 序列化基类指针,实际存储的是 Circle}{std::ifstreamifs("shape.bin");boost::archive::binary_iarchiveia(ifs);ia>>loaded_shape;// 反序列化,自动创建 Circle 对象}loaded_shape->draw();// 输出: Circle r=5return0;}4.3 、版本控制
当类的结构发生变化(如增加、删除或重命名成员)时,版本控制至关重要。serialize函数的第二个参数version就是为此设计的。
classMyClass{intoldData;std::string newData;// 在版本1中添加的成员template<classArchive>voidserialize(Archive&ar,constunsignedintversion){ar&oldData;if(version>=1){ar&newData;// 只有存档版本>=1时,才序列化此成员}// 对于版本>当前版本的情况,可以在这里添加加载旧数据的逻辑}};// 告诉 Boost 这个类的当前版本号BOOST_CLASS_VERSION(MyClass,1)5、 高级技巧与最佳实践
5.1、 拆分save和load
对于非常复杂的类,或者序列化和反序列化逻辑差异很大时,可以将serialize拆分为独立的save和load函数。
template<classArchive>voidsave(Archive&ar,constunsignedintversion)const{ar&data;}template<classArchive>voidload(Archive&ar,constunsignedintversion){ar&data;// 可以在这里做一些初始化工作}BOOST_SERIALIZATION_SPLIT_MEMBER()// 这个宏必须放在类定义末尾5.2、 序列化到内存缓冲区
有时你需要将对象序列化到内存,而不是文件,例如用于网络发送。
#include<sstream>#include<boost/archive/text_oarchive.hpp>#include<boost/archive/text_iarchive.hpp>std::stringstream ss;// 内存流// 序列化到字符串流{boost::archive::text_oarchiveoa(ss);oa<<myObject;}std::string serialized_str=ss.str();std::cout<<"Serialized data: "<<serialized_str<<std::endl;// 从字符串流反序列化{std::stringstreamss2(serialized_str);boost::archive::text_iarchiveia(ss2);ia>>myRestoredObject;}5.3、性能优化
- 使用二进制存档:这是提升速度、减小体积最直接的方法。
- 预分配容器大小:对于
std::vector等,在load函数中可以先读取大小并reserve,避免多次重新分配。 - 避免序列化冗余数据:只序列化真正需要持久化的状态数据,计算得出的临时数据可以忽略。
6、常见问题与陷阱
- 缺少默认构造函数:反序列化时,对象是先默认构造,然后通过
load填充数据。因此可序列化的类通常需要一个公开或受保护的默认构造函数。 - 指针和深拷贝:序列化一个裸指针默认只保存它的地址值,这通常不是你想要的结果。对于拥有所有权的指针,应使用
boost::serialization::shared_ptr或unique_ptr(需要 C++11 及以上支持)来确保深拷贝和正确的内存管理。 - 静态成员变量:静态成员不属于对象状态,不会被自动序列化。如果需要保存,必须手动处理。
- 循环引用:对象图中存在循环引用时,使用
shared_ptr可以正确序列化和反序列化,避免内存泄漏。 - 跨平台兼容性:
- 二进制存档:在 x86 和 ARM 等不同字节序的机器间不兼容。解决方案是使用文本或 XML 存档,或者在序列化前统一转换为网络字节序。
- 编译器与 Boost 版本:尽量保证序列化和反序列化两端使用相同版本的编译器和 Boost 库,特别是对于复杂的模板类。
7、总结
boost::archive作为 Boost.Serialization 库的引擎,为 C++ 提供了工业级、非侵入式的序列化能力。通过定义简单的serialize函数,开发者可以轻松地将复杂的对象图持久化到文件、内存或网络。
核心优势:
- 类型安全:在编译期检查序列化逻辑。
- 非侵入式:可以通过友元或自由函数为第三方类添加序列化支持,无需修改其源码。
- 高度可扩展:支持自定义存档格式、序列化优化和版本管理。
- 与 STL 深度集成:标准容器和智能指针都能完美工作。
在选择存档格式时,应根据需求权衡:
- 调试/互操作→ 选XML或文本。
- 性能/存储空间→ 选二进制。
二、使用步骤
1、下载库
访问链接https://www.boost.org/
2、Visual Studio新建工程
3、将源码解压到工程目录下
新建一个main.cpp文件
将下载的压缩包解压到main.cpp同级目录,并改名为boost
4、添加路径
5、测试代码
#include<iostream>#include<fstream>#include<string>#include<vector>#include<boost/serialization/string.hpp>#include<boost/serialization/vector.hpp>#include<boost/serialization/access.hpp>#include<boost/archive/binary_oarchive.hpp>#include<boost/archive/binary_iarchive.hpp>usingnamespacestd;// ======================// 1. 自定义结构体(可序列化)// ======================structStudent{intid;string name;vector<int>scores;// 让 boost 能访问私有成员friendclassboost::serialization::access;// 序列化函数(固定写法)template<classArchive>voidserialize(Archive&ar,constunsignedintversion){ar&id;// 序列化/反序列化 idar&name;// 序列化/反序列化 namear&scores;// 序列化/反序列化 vector}};// ======================// 2. 序列化:对象 → 文件// ======================voidsave(constStudent&s,conststring&filename){ofstreamofs(filename,ios::binary);boost::archive::binary_oarchiveoa(ofs);oa<<s;// 写入对象cout<<"序列化完成!"<<endl;}// ======================// 3. 反序列化:文件 → 对象// ======================voidload(Student&s,conststring&filename){ifstreamifs(filename,ios::binary);boost::archive::binary_iarchiveia(ifs);ia>>s;// 读取对象cout<<"反序列化完成!"<<endl;}// ======================// 主函数测试// ======================intmain(){// 1. 创建测试对象Student stu;stu.id=1001;stu.name="张三";stu.scores={90,85,95};// 2. 序列化到文件save(stu,"student.dat");// 3. 清空,反序列化回来Student stu2;load(stu2,"student.dat");// 4. 输出验证cout<<"ID: "<<stu2.id<<endl;cout<<"姓名: "<<stu2.name<<endl;cout<<"成绩: ";for(intx:stu2.scores)cout<<x<<" ";cout<<endl;return0;}运行结果
序列化完成! 反序列化完成! ID:1001姓名:张三 成绩:908595C:\Users\徐鹏\Desktop\新建文件夹\Project2\x64\Debug\Project2.exe(进程3160)已退出,代码为0(0x0)。 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。 按任意键关闭此窗口...