boost Serialization序列化

boost Serialization序列化

一个输出存档类似于一个输出数据流。数据可以用操作符 << 或 & 保存到存档中:

ar << data;
ar & data;
一个输入存档则类似于一个输入数据流。数据可以用操作符 >> 或 & 从存档中取出。

ar >> data;
ar & data;
对简单的数据类型调用这些操作符时,数据就保存到存档中或从存档中取出。而对类数据类型调用时,则将调用类的 serialize 函数。各个 serialize 函数则使用以上操作符来保存/取出类的数据成员。这个过程将递归执行直至类中所含的所有数据都被保存/取出。

一个非常简单的例子
在保存和取出类的数据成员的 serialize 中将会用到这些操作符。
本库中包含一个名为 demo.cpp 的程序,它示范了如何使用这个系统。下面我们从这个程序中摘录部分代码来示范如何使用本库的最简单的一种情形。


#include <fstream>

// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

/
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
friend class boost::serialization::access;
// 如果类 Archive 是一个输出存档,则操作符 & 被定义为 <<. 同样,如果类 Archive
// 是一个输入存档,则操作符 & 被定义为 >>.
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};

int main() {
// 创建并打开一个输出用的字符存档
std::ofstream ofs("filename");

// 创建类实例
const gps_position g(35, 59, 24.567f);

// 保存数据到存档
{
boost::archive::text_oarchive oa(ofs);
// 将类实例写出到存档
oa << g;
// 在调用析构函数时将关闭存档和流
}

// ... 晚些时候,将类实例恢复到原来的状态
gps_position newg;
{
// 创建并打开一个输入用的存档
std::ifstream ifs("filename", std::ios::binary);
boost::archive::text_iarchive ia(ifs);
// 从存档中读取类的状态
ia >> newg;
// 在调用析构函数时将关闭存档和流
}
return 0;
}

对于每一个要通过序列化来保存的类,都必须要有一个函数用于保存类中所有定义该类状态的成员。对于每一个要通过序列化来取出的类,都必须要有一个函数用于将该类中的成员以保存的顺序重新取出。以上述例子中,这些函数由模板成员函数 serialize 生成。

非介入式版本
以上例子是介入式的。即是说,它要求对被序列化的类进行修改。有时这种做法是有困难的。我们的系统允许采用另外一种等价的方法:


#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

class gps_position
{
public:
int degrees;
int minutes;
float seconds;
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};

namespace boost {
namespace serialization {

template<class Archive>
void serialize(Archive & ar, gps_position & g, const unsigned int version)
{
ar & g.degrees;
ar & g.minutes;
ar & g.seconds;
}

} // namespace serialization
} // namespace boost
在这个例子中,生成的序列化函数不是 gps_position 类的成员。这两种方式的函数其实都一样。

非介入式序列化的主程序允许对那些不能修改定义的类进行序列化。为了可以做到这一点,类必须暴露足够的信息来重新构建类的状态。在上例中,我们假定这个类带有 public 成员 - 这不常出现。只有暴露了用于保存和恢复类状态的足够信息的类才可以序列化且不修改类的定义。

可序列化的成员
一个带有可序列化成员的可序列化类看起来就象这样:

class bus_stop{ friend class boost::serialization::access; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & latitude; ar & longitude; } gps_position latitude; gps_position longitude;protected: bus_stop(const gps_position & lat_, const gps_position & long_) : latitude(lat_), longitude(long_) {}public: bus_stop(){} // 请见 Scott Meyers 的 Effective C++ 中的 item # 14. // 避免基类中的非虚拟析构函数 virtual ~bus_stop(){}};即,类类型成员可以象简单类型成员一样序列化。

注意,用一个存档操作符保存一个 bus_stop 实例,将会调用 serialize 函数来保存 latitude 和 longitude. 它们又会通过调用 gps_position 定义中的 serialize 来保存数据。这样,应用程序只需对根项使用一个存档操作符就可以保存整个数据结构。

派生类
派生类将包含对其基类的序列化。


#include <boost/serialization/base_object.hpp>

class bus_stop_corner : public bus_stop
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// 序列化基类信息
ar & boost::serialization::base_object<bus_stop>(*this);
ar & street1;
ar & street2;
}
std::string street1;
std::string street2;
virtual std::string description() const
{
return street1 + " and " + street2;
}
public:
bus_stop_corner(){}
bus_stop_corner(const gps_position & lat_, const gps_position & long_,
const std::string & s1_, const std::string & s2_
) :
bus_stop(lat_, long_), street1(s1_), street2(s2_)
{}
};

注意派生类中的基类序列化。不要直接调用基类的 serialize 函数。这样做看起来可以工作,但是会跳过那些跟踪冗余存储的代码。它还会跳过将类的版本信息写入到存档。由于这些原因,建议总是将成员 serialize 函数定义为私有的。声明 friend boost::serialization::access 可以授权 serialization 库访问私有的成员变量和函数。


指针
假设我们用一个公共汽车站的数组来定义一条公共汽车线路。给定以下条件:
我们可能有几类公共汽车站 (请记住 bus_stop 是一个基类)
一个给定的 bus_stop 可能出现在不止一条的线路上。
比较方便的方法是将一条公共汽车线路表示为 bus_stop 指针的数组。

class bus_route
{
friend class boost::serialization::access;
bus_stop * stops[10];
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
int i;
for(i = 0; i < 10; ++i)
ar & stops[i];
}
public:
bus_route(){}
};

stops 数组中的每个成员都要被序列化。但是这些成员都是指针 - 这意味着什么? 这次序列化的整个对象要可以在其它地方和其它时间重新构造出原来的数据结构。为了对一个指针达到此目的,只保存指针的值是不够的,还必须保存指针所指的对象。当该成员稍后被导入时,新创建一个新的对象并将一个新的指针导入到类成员中。
如果同一个指针被序列化超过一次以上,则只会有一个实例被添加到存档中。在重新读回时,该指针的数据不会重新读回。唯一发生的操作是将第二个指针设置为与第一个相等。

请注意,在这个例子中,数组包含的是多态指针。即数组中的每个元素所指向的是几种可能的公共汽车站类型中的某一个。所以,在保存指针时,还要保存关于类的某些标识信息。在导入该指针时,类的标识也要读入以构造相对应的类实例。最后才可以将数据导入到正确类型的新建实例中。正如在 demo.cpp 中看到的,通过基类指针来对派生类指针进行序列化时,要求显式列出要序列化的派生类类型。这被称为派生类 "登记" 或 "导出"。有关该要求及其实现方法的详情,请见 这里 的解释。

以上这些都是由 serialization 库自动实现的。要通过指针进行对象的保存和导入,上述代码就是需要做的所有工作。


数组
上一节的例子中的写法其实并不需要这么复杂。serialization 库会检测序列化的对象是否数组,并生成与上述等价的代码。所以上述代码可以简化为:

class bus_route
{
friend class boost::serialization::access;
bus_stop * stops[10];
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & stops;
}
public:
bus_route(){}
};

STL 集合
上述例子中使用了一个数组成员。更多的情况下,应用程序会用一个STL集合来实例该目的。serialization 库包含了对所有STL类进行序列化的代码。所以,以下写法也可以正确工作。

#include <boost/serialization/list.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & stops;
}
public:
bus_route(){}
};

类的版本化
假设我们很满意我们的 bus_route 类,用它构建了一个程序并且提交了这个产品。不久后,该程序需要加强并将 bus_route 类改为包含该线路的司机姓名。新的版本如下:


#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
std::string driver_name;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & driver_name;
ar & stops;
}
public:
bus_route(){}
};

好的,我们完成了这项工作。除了 ... 如果使用这个程序的用户已经有了一组由旧程序创建的文件,会怎么样?这些旧文件如何与我们的新程序一起使用?
通常,serialization 库为每个序列化的类在存档中保存一个版本号。版本号的缺省值为0。在导入存档时,将读入所保存的版本号。上述代码可以修改为:


#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
std::string driver_name;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// 只对新的存档保存/导入 driver_name
if(version > 0)
ar & driver_name;
ar & stops;
}
public:
bus_route(){}
};

BOOST_CLASS_VERSION(bus_route, 1)

通过对每个类进行版本化经,就可以无需维护文件的版本信息。即是说,文件的版本是由它所包含的类的版本所组成的。本系统允许程序总是可以与旧版本程序所创建的存档兼容,而无需做出比上述例子更多的努力。
将 serialize 分为 save/load
serialize 函数是简单明了的,并保证了类的成员以相同的顺序进行保存和导入 - 这是序列化系统的关键。不过,有时候保存和导入操作并不象前面的例子那样完全类似。例如,当一个类经过多个版本的发展后就可能发生这种情况。上述例子中的类可以重写为:

#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/split_member.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
std::string driver_name;
template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
// 注意,保存时总是保存最新版本
ar & driver_name;
ar & stops;
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
if(version > 0)
ar & driver_name;
ar & stops;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
public:
bus_route(){}
};

BOOST_CLASS_VERSION(bus_route, 1)

宏 BOOST_SERIALIZATION_SPLIT_MEMBER() 将生成代码,根据存档是用于保存还是导入来分别调用 save 和 load .
存档
我们在这里的讨论集中于为类增加序列化的能力。被序列化的数据的实际表现形式则由 archive 类实现。因此,序列化数据流是由类的序列化以及选定的存档类共同产生的。一个关键的设计决策是,让这两个组件互不相关。这样使得任意的序列化规格可以与任意的存档一起使用。
在本指南中,我们已经用了一种存档类 - text_oarchive 用于保存而 text_iarchive 用于导入。文本存档以文本方式保存数据,可以在不同平台间移植。除了文本存档,本库还包含了二进制数据的存档类和XML格式数据的存档类。所有存档类的接口都是统一的。一旦为一个类定义了序列化,那么该类就可以被序列化到任意类型的存档。

如果当前的存档类没有提供特定应用程序所需的属性、格式或行为,你可以创建一个新的存档类,或者从已有存档类中派生。在本手册的后面会有相关介绍。

例子的列表
demo.cpp
这是一个本指南使用的完整例子。它:
创建了了一个由不同类型的车站、线路和调度表组成的结构
显示该结构
将该结构序列化到一个名为 "testfile.txt" 的文件,只用一条语句
再恢复到另一个结构中
显示恢复的结构
该程序的输出 充分证实了对于序列化系统的所有原来声明的要求都在本系统中达到了。存档文件的内容 也可以被显示,因为序列化文件是 ASCII 文本的。
demo_xml.cpp
这是前一个例子的变种,增加了对 xml 存档的支持。需要增加一些包装用的宏来将数据项名字与相应的xml tag关联起来。更多的信息请见 Name-Value Pairs. 这里 是一个 xml 存档的样例。
demo_xml_save.cpp 和 demo_xml_load.cpp
请注意,虽然我们的例子是在同一个程序中进行数据的保存和导入,但这只不过是为了举例的方便。通常,存档可能是也可能不是在创建存档的程序中进行导入恢复的。
聪明的读者可以会注意到,这些例子包含了一个微妙但却重要的缺陷。它们存在内存泄漏。公共汽车站在主程序中创建。而调用表会多次引用这些车站。在主程序的最后,要先销毁调用度,然后再销毁车站。看起来这没有问题。但是对于其数据项由存档中导入创建的结构 new_schedule 呢?它包含独立的车站集合,在调用表之外没有任何引用。它们不会在程序的任何地方被销毁 - 这就是内存泄漏。

有几种方法来改正。一种方法是,显式地管理车站对象。但是,更可靠更清晰的方法是,使用 shared_ptr 而不是裸指针。与实现了标准库的序列化一样,serialization 库也包含了对 boost::shared_ptr 的序列化实现。有了这个,就很容易修改以上例子来排除内存泄漏。这就作为练习留给读者吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值