Boost.Serialization 提供了多个归档类,如 boost::archive::text_oarchive 类,它定义在 boost/archive/text_oarchive.hpp 文件中。 boost::archive::text_oarchive,可将对象序列化为文本流。
boost/archive/binary_iarchive.hpp定义了 binary_iarchive 类。这个类用于从二进制格式的数据流中读取数据并反序列化对象。
boost/archive/binary_iarchive.hpp定义了 binary_oarchive 类。这个类用于将对象序列化为二进制格式的数据流。通过binary_oarchive和binary_iarchive可以实现网络通信形式的数据序列化/反序列化。例子如下:
std::vector<char> pack(const developer& serializable) {
try {
std::vector<char> data;
stream<back_insert_device<std::vector<char>>> stream(data);
binary_oarchive archive(stream, no_header | no_codecvt);
const developer* pSerializable = &serializable;
archive << pSerializable;
stream.flush();
return data;
}
catch (const archive_exception& e) {
printf("serialization::pack failed:%s\n", e.what());
return {};
}
}
developerPtr unpack(const std::vector<char>& data) {
try {
stream<array_source> stream(array_source{ data.data(), data.size() });
binary_iarchive archive(stream, no_header | no_codecvt);
developer* serializable;
archive & serializable;
return developerPtr(serializable);
}
catch (const archive_exception&) {
return nullptr;
}
}
1、将基本数据类型保存到文件后并读取显示
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>
void save()
{
std::ofstream file("archiv.txt");
boost::archive::text_oarchive oa(file);
int i = 1;
oa << i;
}
void load()
{
std::ifstream file("archiv.txt");
boost::archive::text_iarchive ia(file);
int i = 0;
ia >> i;
std::cout << i << std::endl;
}
int main()
{
save();
load();
}
2、将自定义数据类型保存到文件并保存。
自定义的数据类型需要增加函数签名,用于支持对象的序列化和反序列化操作。
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
模板参数 Archive:这个模板参数代表了一个归档类(archive class),它可以是不同类型的归档实现,例如文本归档、二进制归档或者 XML 归档等。归档对象负责将数据保存到某种格式的文件或从某种格式的文件加载数据。
成员函数 serialize:这是一个成员函数,通常定义在需要被序列化的类中。它告诉编译器如何将该类的对象转换为一系列字节(序列化)以及如何从这些字节重新构建对象(反序列化)。通过重载这个函数,你可以自定义序列化的行为,包括选择哪些成员变量参与序列化过程。
参数 version:这是一个无符号整数,用来表示当前类版本号。这有助于处理类结构的变化——如果类的结构在未来发生了变化(比如添加了新成员或移除了旧成员),你可以根据版本号采取不同的序列化逻辑来兼容旧的数据格式。
序列化与反序列化
当你调用 ar & member; 时:
如果 ar 是一个输出归档(如 boost::archive::text_oarchive),那么 & 操作符会触发序列化过程,即将 member 的值写入归档。
如果 ar 是一个输入归档(如 boost::archive::text_iarchive),那么 & 操作符会触发反序列化过程,即从归档读取数据并赋值给 member。
为了序列化用户定义类型的对话, serialize() 函数必须定义,它在对象序列化或从字节流中恢复是被调用。 由于 serialize () 函数既用来序列化又用来恢复数据, Boost.Serialization 除了 << 和 >> 之外还提供了 & 操作符。如果使用这个操作符,就不再需要在 serialize() 函数中区分是序列化和恢复了。
serialize () 在对象序列化或恢复时自动调用。它应从来不被明确地调用,所以应生命为私有的。 这样的话, boost::serialization::access 类必须被声明为友元,以允许 Boost.Serialization 能够访问到这个函数。
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>
struct Test
{
int _a;
int _b;
Test() : _a(0), _b(0) {}
Test(int a,int b) : _a(a), _b(b){}
template<typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & _a;
ar & _b;
}
};
BOOST_CLASS_VERSION(Test, 1)
void save()
{
std::ofstream file("archiv.txt");
boost::archive::text_oarchive oa(file);
Test t {10, 20};
oa << t;
}
void load()
{
std::ifstream file("archiv.txt");
boost::archive::text_iarchive ia(file);
Test t;
ia >> t;
std::cout << t._a << " " << t._b << std::endl;
}
int main()
{
save();
load();
}
BOOST_CLASS_VERSION表示声明当前的版本号。只有成员在serialize函数中进行&操作后,此成员才会进行序列化操作,否则反序列化后为空。
增加string、vector、map、boost::variant等变量后,为了序列化这些标准库的类型的对象,头文件必须相应添加#include <boost/serialization/string.hpp> 、#include <boost/serialization/vector.hpp> 、#include <boost/serialization/map.hpp>、 <boost/serialization/map.hpp>。Boost.Serialization 为许多 C++ 标准库类定义了 serialize () 函数,例子如下:
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>
#include <boost/serialization/string.hpp>
#include <boost/serialization/variant.hpp>
struct Test
{
int _a;
int _b;
std::string _strData;
boost::variant<int, std::string> _varData;
Test() : _a(0), _b(0) {}
Test(int a,int b) : _a(a), _b(b){}
Test(int a,int b, std::string strData, boost::variant<int, std::string> varData)
: _a(a), _b(b), _strData(strData), _varData(varData) {}
private:
// 将 boost::serialization::access 声明为友元类
friend class boost::serialization::access;
template<typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & _a;
ar & _b;
if (version > 0){
ar & _strData;
ar & _varData;
}
}
};
BOOST_CLASS_VERSION(Test, 1)
void save()
{
std::ofstream file("archiv.txt");
boost::archive::text_oarchive oa(file);
boost::variant<int, std::string> var(1);
std::string strDatra = "test";
var = "test23";
Test t {10, 20, strDatra, var};
oa << t;
}
void load()
{
std::ifstream file("archiv.txt");
boost::archive::text_iarchive ia(file);
Test t;
ia >> t;
std::cout << "a:" << t._a
<< ", b:" << t._b
<< ", str:" << t._strData
<< ", var:" << t._varData
<< std::endl;
}
int main()
{
save();
load();
}
Boost.Serialization 还能序列化指针和引用。 由于指针存储对象的地址,序列化对象的地址没有什么意义,而是在序列化指针和引用时,对象的引用被自动地序列化。
形式上序列化和反序列化的时是指针,实际上是对指针指向的对象进行的操作。
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
person *p = new person(31);
oa << p;
std::cout << std::hex << p << std::endl;
delete p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person *p;
ia >> p;
std::cout << std::hex << p << std::endl;
std::cout << std::dec << p->age() << std::endl;
delete p;
}
int main()
{
save();
load();
}
上面的应用程序创建了一个新的 person 类型的对象,使用 new 创建并赋值给指针 p 。 是指针p而不是 对象*p 被序列化了。Boost.Serialization 自动地通过 p 的引用序列化对象本身而不是对象的地址。
如果归档被恢复, p 不必指向相同的地址。 而是创建新对象并将它的地址赋值给 p 。 Boost.Serialization 只保证对象和之前序列化的对象相同,而不是地址相同。
智能指针同理。如下例子中使用了智能指针 boost::scoped_ptr 来管理动态分配的 person 类型的对象。 为了序列化这样的指针,必须包含 <boost/serialization/scoped_ptr.hpp> 头文件。
在使用 boost::shared_ptr 类型的智能指针的时候需要序列化,那么必须包含 <boost/serialization/shared_ptr.hpp> 头文件。
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/scoped_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
boost::scoped_ptr<person> p(new person(31));
oa << p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
boost::scoped_ptr<person> p;
ia >> p;
std::cout << p->age() << std::endl;
}
int main()
{
save();
load();
}
使用二进制流的单继承例子:
#include <boost/iostreams/stream.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <iostream>
#include <sstream>
#include <string>
using namespace boost::iostreams;
using namespace boost::archive;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
using developerPtr = std::shared_ptr<developer>;
std::vector<char> pack(const developer& serializable) {
try {
std::vector<char> data;
stream<back_insert_device<std::vector<char>>> stream(data);
binary_oarchive archive(stream, no_header | no_codecvt);
const developer* pSerializable = &serializable;
archive << pSerializable;
stream.flush();
return data;
}
catch (const archive_exception& e) {
printf("serialization::pack failed:%s\n", e.what());
return {};
}
}
developerPtr unpack(const std::vector<char>& data) {
try {
stream<array_source> stream(array_source{ data.data(), data.size() });
binary_iarchive archive(stream, no_header | no_codecvt);
developer* serializable;
archive & serializable;
return developerPtr(serializable);
}
catch (const archive_exception&) {
return nullptr;
}
}
int main()
{
developer d(31, "C++");
std::vector<char> vDatas = pack(d);
developerPtr dptr = unpack(vDatas);
std::cout << "language:" << dptr->language() << ", age:" << dptr->age() << std::endl;
}
person 和 developer 这两个类都包含有一个私有的 serialize () 函数, 它使得基于其他类的对象能被序列化。 由于 developer 类继承自 person 类, 所以它的 serialize () 函数必须确保继承自 person 属性也能被序列化。
继承自基类的属性被序列化是通过在子类的 serialize () 函数中用 boost::serialization::base_object () 函数访问基类实现的。 在例子中强制要求使用这个函数而不是 static_cast 是因为只有 boost::serialization::base_object () 才能保证正确地序列化。
动态创建对象的地址可以被赋值给对应的基类类型的指针。 下面的例子演示了 Boost.Serialization 还能够正确地序列化它们。
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
virtual int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
BOOST_CLASS_EXPORT(developer)
void save()
{
boost::archive::text_oarchive oa(ss);
person *p = new developer(31, "C++");
oa << p;
delete p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person *p;
ia >> p;
std::cout << p->age() << std::endl;
delete p;
}
int main()
{
save();
load();
}
应用程序在 save () 函数创建了 developer 类型的对象并赋值给 person* 类型的指针,接下来通过 << 序列化。
为了让 Boost.Serialization 识别将要序列化的 developer 类型的对象,即使指针是 person* 类型的对象。 developer 类需要相应的声明。 这是通过这个 BOOST_CLASS_EXPORT 宏实现的,它定义在 boost/serialization/export.hpp 文件中。 因为 developer 这个数据类型没有指针形式的定义,所以 Boost.Serialization 没有这个宏就不能正确地序列化 developer 。
如果子类对象需要通过基类的指针序列化,那么 BOOST_CLASS_EXPORT 宏必须要用。
由于静态注册的原因, BOOST_CLASS_EXPORT 的一个缺点是可能有些注册的类最后是不需要序列化的。 Boost.Serialization 为这种情况提供一种解决方案。
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
virtual int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
oa.register_type<developer>();
person *p = new developer(31, "C++");
oa << p;
delete p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
ia.register_type<developer>();
person *p;
ia >> p;
std::cout << p->age() << std::endl;
delete p;
}
int main()
{
save();
load();
}
上面的应用程序没有使用 BOOST_CLASS_EXPORT 宏,而是调用了 register_type () 模板函数。 需要注册的类型作为模板参数传入。
请注意 register_type () 必须在 save () 和 load () 都要调用。
register_type () 的优点是只有需要序列化的类才注册。 比如在开发一个库时,你不知道开发人员将来要序列化哪些类。 当然 BOOST_CLASS_EXPORT 宏用起来简单,可它却可能注册那些不需要序列化的类型。
为了为了在编译时注册类型信息,以便于运行时进行类型反射(type reflection)和序列化支持,确保类型可以在不同模块之间正确地导出、导入,并且能够在运行时被识别和处理,需要做如下处理:
#define TYPE_REFLECT_REGISTER(T, K) \
static type_reflect_register<T> g_type_reflect_register_##K; \
#define REGISTER_TYPE_HEADER(T, K) \
FORCE_EXPORT_HEADER(export_##K##_symbol) \
BOOST_CLASS_EXPORT_KEY2(T, #K) \
/**/
#define REGISTER_TYPE_SOURCE(T, K) \
FORCE_EXPORT_SOURCE(export_##K##_symbol) \
BOOST_CLASS_EXPORT_IMPLEMENT(T) \
TYPE_REFLECT_REGISTER(T, K) \
TYPE_REFLECT_REGISTER(T, K)
作用:这个宏创建了一个静态对象 g_type_reflect_register_##K,其类型为 type_reflect_register。这个对象会在程序启动时自动构造,从而触发某种形式的类型注册机制。
目的:通过这种方式,可以在全局范围内注册类型 T,使得它可以在运行时被识别。这里的 K 通常是一个唯一的标识符,用于区分不同的注册实例。
REGISTER_TYPE_HEADER(T, K)
作用:FORCE_EXPORT_HEADER 确保符号在头文件中被正确导出,以便链接器可以找到该类型的定义。
BOOST_CLASS_EXPORT_KEY2(T, #K) 是 Boost.Serialization 提供的一个宏,用于为类 T 分配一个唯一的关键字(这里是 #K,即字符串化的 K)。这有助于在多个库或模块之间唯一标识类型 T。
REGISTER_TYPE_SOURCE(T, K)
作用:FORCE_EXPORT_SOURCE 确保符号在源文件中被正确导出,以便链接器可以找到该类型的实现。
BOOST_CLASS_EXPORT_IMPLEMENT(T) 实现了之前由 BOOST_CLASS_EXPORT_KEY2 声明的导出逻辑。它告诉 Boost.Serialization 如何实际导出类型 T 的序列化信息。
TYPE_REFLECT_REGISTER(T, K) 再次调用以确保类型 T 在运行时被注册到类型反射系统中。
这三个宏共同工作,实现了以下功能:
类型注册:确保每个类型 T 都有一个唯一的标识符 K,并且可以在运行时被识别和处理。
跨模块导出/导入:通过 BOOST_CLASS_EXPORT_KEY2 和 BOOST_CLASS_EXPORT_IMPLEMENT,保证类型 T 的序列化信息能够被正确导出并在其他模块中导入。
强制导出符号:使用FORCE_EXPORT_HEADER 和 FORCE_EXPORT_SOURCE 来确保必要的符号在编译和链接过程中被正确处理,避免符号未定义错误。
这种模式特别适用于需要动态加载模块或插件的应用程序,以及那些依赖于运行时类型信息的框架,如序列化库、RPC 系统等。通过这些宏,开发者可以简化类型注册的过程,同时保持良好的模块化设计。
#include <boost/iostreams/stream.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>
#include <string>
using namespace boost::iostreams;
using namespace boost::archive;
#ifdef _WIN32
#define FORCE_EXPORT_HEADER(name) \
__pragma(comment(linker, "/export:_"#name)) \
#define FORCE_EXPORT_SOURCE(name) \
extern "C" void name() {} \
#else
#define FORCE_EXPORT_HEADER(name) \
//__pragma(comment(linker, "/export:_"#name))
#define FORCE_EXPORT_SOURCE(name) \
extern "C" void name() {}
#endif
#define REGISTER_TYPE_HEADER(T, K) \
FORCE_EXPORT_HEADER(export_##K##_symbol) \
BOOST_CLASS_EXPORT_KEY2(T, #K) \
/**/
#define REGISTER_TYPE_SOURCE(T, K) \
FORCE_EXPORT_SOURCE(export_##K##_symbol) \
BOOST_CLASS_EXPORT_IMPLEMENT(T) \
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
using developerPtr = std::shared_ptr<developer>;
std::vector<char> pack(const developer& serializable) {
try {
std::vector<char> data;
stream<back_insert_device<std::vector<char>>> stream(data);
binary_oarchive archive(stream, no_header | no_codecvt);
const developer* pSerializable = &serializable;
archive << pSerializable;
stream.flush();
return data;
}
catch (const archive_exception& e) {
printf("serialization::pack failed:%s\n", e.what());
return {};
}
}
REGISTER_TYPE_HEADER(developer, developer);
REGISTER_TYPE_SOURCE(developer, developer);
developerPtr unpack(const std::vector<char>& data) {
try {
stream<array_source> stream(array_source{ data.data(), data.size() });
binary_iarchive archive(stream, no_header | no_codecvt);
developer* serializable;
archive & serializable;
return developerPtr(serializable);
}
catch (const archive_exception&) {
return nullptr;
}
}
int main()
{
developer d(31, "C++");
std::vector<char> vDatas = pack(d);
developerPtr dptr = unpack(vDatas);
std::cout << "language:" << dptr->language() << ", age:" << dptr->age() << std::endl;
}