[转]Boost 序列化

没有工作前,不知道序列化的作用。虽然那时候学习《Programming with MFC》的时候,也知道CArchive,CObject等东西,但是没有太意识到其作用。但是,如前所述,我工作的第一件事情,就是了解公司的序列化 类。从当时的一无所知,到现在也慢慢理解了一些序列化的作用了。说起来,自从工作以来因为当时做文件系统后后来的程序补丁,我应经在公司的序列化类以外额 外实现了两个独立的序列化类了,分别是文件序列化和内存序列化。

  这里也顺面讲讲序列化吧,虽然不算太难,也不算太复杂,但是这样越是基础的东西越是支持着程序:)没有还真不行。

   序列化的作用包括,实现操作系统,硬件平台无关的数据保存和传输。从网络传输角度来讲解决诸如大头,小头等问题。目前我自己的感受还有,对于保存数据和 读取数据使用同一套接口,简化了数据结构的管理,方便使用。虽然,序列化的作用一般是用来做永久保存的,在《深入浅出MFC》中,侯捷就将其中的序列化技 术讲解称为“VC++六大关键技术”,事实上,序列化的作用不仅仅如此,比如我以前不就写过一个内存序列化类吧:)公司还有一个网络序列化类,即便在 MFC中,也有可以序列化的CMemFile,事实上,序列化还是一种二进制的交流方式。:)

  最典型的网络程序,就更好理解了,网络上 交流的其实都是二进制的数据,从一端到另外一端,大家要有统一的格式支持才能和谐-_-!不仅仅是字符串的编码需要一致,字节的顺序需要一致,每个位置是 什么值自然也需要一一指定好,一般而言,对于网络程序,在我们公司,对于序列化成一个包,我们叫打包,从一个网络包反序列化(似乎也有人叫串行化),我们 叫解包。

  这种二进制交流的方式不仅限于网络程序,任何程序间的交流都有可能用到,我碰到另外一个典型情况是一个Python脚本希望将 一些数据经过一段C++程序,然后再放到另外一段Python脚本中去,这时当然可以通过Python的C API将所有的数据都一个一个转成C变量保存下来然后传出去,由C++程序运行时保存,然后再通过Python的C API一一转成Python的变量,这样做有很多不好的地方,其一是Python脚本间传递的数据对于C++程序来说不透明,任何改变都需要C++程序进 行相应的更改,另外是每个变量都通过Python的C API去转换效率不高。其实这里就可以利用序列化成二进制数据来传输,其上两个缺点都会消失。顺面提一下,在Python中可以通过struct库的 pack,unpack函数进行简单数据的序列化,通过marshal库来进行复杂对象的序列化(可是这个库好像已经不被推荐,但是我当时在实际工作中使 用的就是这个),有更先进的pickle(有更快的C语言版本cPickle),shelve可用。从这点来看,又会感叹Python的库实在是太多,往 往不是没有你想要的库,而是由于库太多,你不知道哪个好用-_-!

这里找到了一些序列化的库,MFC的序列化库不能在Linux下用,剩下可以尝试的还有s11n,CommonC++,和boost的序列化库了。 特别要提到的就是boost的库。一个序列化的库也写了230多K,真是服了。基本上我在公司写的序列化类也就一个文件一个类,也就1K,包括了所有的基 本结构。很显然,boost的野心是很大的。出于对准标准库的尊敬。自然优先boost。特别要提到的是,Boost有所有我需要的东西-_-!并且我发 现Boost的ASIO库就是我以前计划完成的目标。以后有特别有文章详细提到。

  这里感谢为Boost库进行中文文档性工作的哥们,实在是感谢,虽然我常常以学英文为由去参看原文文档,甚至还阅读过基本原著,但是在文中引用的时候插入一篇的英文似乎是不太合适的,并且当我需要很快知道答案的时候,中文能够让我更快的处理,感谢你们。

  以下引号中内容摘自Boost中文文档1.37.0版本:

  这里有个对序列化比较正统的解释

   “这里,我们用术语 "serialization序列化" 来表示将任意一组C++数据结构解构为一串字节的、可逆的过程。这样的系统可用于在另一个程序上下文中重新构建一个等价的结构。根据不同的上下文,它可以 用来实现对象持久化、远程参数传递或其它功能。在本系统中,我们使用术语 "archive存档" 来指代这个字节流的特定表现。它可以是一个二进制数据文件、文本文件、XML或其它由本库的用户所创建的东西。”

  注意,序列化的作用如我之前所述有:实现对象持久化、远程参数传递

  我们对于这个系统的目标是:

  代码的可移植性 - 只依赖于 ANSI C++ 所提供的功能。

编缉推荐阅读以下文章

  • 序列化支持(4)—Boost的序列化库的强大之处
  • 序列化支持(3)—Boost的序列化库的使用
  • 序列化支持(1)—前言

  代码的经济性 - 使用C++的一些特性,如 RTTI, 模板, 和多重继承等等,以使得代码更短也更易于使用。

  各个类定义版本的无关性。即当一个类的定义更改时,旧文件仍可导入到新版本的类中。

  深的指针保存和恢复。即指针的保存与恢复分别保存和恢复所指的数据。

  对共享数据指针的正确恢复。

  STL容器及其它常用模板的序列化。

  数据的可移植性 - 在一个平台上创建的字节流可以在另一平台上读出。

  类的序列化与存档格式的正交性。即任何文件格式都可用于保存任意一组C++数据结构的序列化信息而无需调整。

  非介入性。可以对不作更改的类进行序列化。即不要求进行序列化的类派生自某个特定基类或者实现特定的成员函数。这一点对于要将序列化应用于某些我们不能或不愿修改的类库中的类来说是十分必要的。

  archive 的接口必须足够简单,以易于创建一种新的存档类型。

  archive 的接口又必须足够丰富,才可以创建出象XML这样风格的存档。

  野心够大,其描述的第1,7点很符合我的需要,第6点是属于额外的好处。

   我们公司的序列化方式是,对于已实现序列化的结构使用其结构的Serialize方法,对于基本结构使用序列化类的Serialize,因为序列化类已 经重载了基本结构的的Serialize实现。我曾经对比过这种方法和MFC的序列化方式,还有stream的使用方式,我感觉假如都使用stream那 样重载<<,>>操作符的方式是最能节省击键数的,并且也足够的形象,并且个人认为,在某种程序上来 说,stringstream就可以做一个简单的Archive实现来用。但是这里有一个比较不方便的就是,都需要先判断是存储还是读取,然后分别调用操 作符,这里就相当于多了一倍的工作量,而公司的方式(统一到Serialize函数),虽然多了函数名的输入,但是对于输入还是输出可以不关心,这是其很 大的优势。当时一下子还分不清孰优孰劣,但是出于亲近C++ 流实现的方式。碰到Boost的重载&操作符方式一下子傻了。。。。呵呵,听了太多的教条,太多的教导告诉我们不要重载操作符进行莫名奇妙的操 作,除了真正用到其操作符原始涵义的时候,我还真从来没有去重载过他们,这里算是见识到了,虽然是重载了与操作符进行序列化运算,有点扭曲,但是,一切是 简单就好,下面的示例你可以看到,这样兼有我上面所述的两种方案的优点。

编缉推荐阅读以下文章

  • 序列化支持(4)—Boost的序列化库的强大之处
  • 序列化支持(3)—Boost的序列化库的使用
  • 序列化支持(1)—前言

  回想起工作当中用了多少CTRL-C,CTRL-V(yy,p)去实现该死的一行又一行的序列化啊,在我们公司吓死人的物品扩展属性中, 有60个以上的字段。也记不清多少次添加成员变量却忘写序列化函数导致的bug,虽然这样的bug都都容易找,但是都出在与同事联合调试的时候,还是浪费 了很多时间。Boost的这种方案仅仅也是简化了实现,并没有办法根除这一点,还是必须说明。虽然其提供了不介入的方式来完成序列化,但是本质上序列化函 数还是需要自己来写,以后要是有种方式能够为用户新添的任何结构或类进行序列化,而不需要用户进行额外的工作时,那才是完美的序列化方案。毕竟,越少的工 作,出错的机会也就越少。

  现在想起来,重复工作最多的几个地方在哪?其一,结构的拷贝构造函数和=操作符的重载,为了安全,公司都是一 个字段一个字段的写,累死人,也容易错。其二,就是一个字段一个字段的写序列化了。其三,目前还没有解决办法,数据库的操作,无论是MySQL的C API还是ODBC,都是有多少个字段(呵呵,多少个“?”)就得bind多少个结构的数据。也是累死人。想想我的两个记日志工作就心寒(其一就是日志服 务器,另外还有监控中心对整个服务器运行情况的日志记录),那个重复工作的啊。还好有vim:)以前还特意讲过怎么用vim来简化我的工作,没有vim我 都吐血了。最好用的就是q的记录功能和ctrl+a的联合使用了(可惜Viemu不支持ctrl+a)

  本来说,Boost的文档属于开 源库中最最详细的一列了,基本上跟着文档走就都能学会了,但是对于初学者来说可能有的地方太过简略,当然,对于熟悉boost的人来说那叫主题吐出,一针 见血。我这里主要摘文档中的例子来讲讲,偶尔发表一下自己的见解,有的地方也跟进实现去看看。毕竟原有的例子仅仅是很简单的。这里自然还是推荐任何学习者 都像我一样,调试其中的每一个例子,而不仅仅是看看而已。

本来来说,Boost的文档属于开源库中最最详细的一列了,基本上跟着文档走就都能学会了,但是对于初学者来说可能有的地方太过简略,当然,对于熟 悉boost的人来说那叫主题吐出,一针见血。我这里主要摘文档中的例子来讲讲,偶尔发表一下自己的见解,有的地方也跟进实现去看看。毕竟原有的例子仅仅 是很简单的。这里自然还是推荐任何学习者都像我一样,调试其中的每一个例子,而不仅仅是看看而已。

  关于Boost的编译和配置假如觉的麻烦,可以下一个自动下载安装的程序来完成,在windows下,从1.35开始我就一直使用此自动安装程序,安装和卸载非常方便,接下来需要做的就仅仅是简单的添加好工作路径就行了。

  以下例子如无特别说明都来自于boost的文档。

  例一:

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#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;
}

编缉推荐阅读以下文章

  • 序列化支持(4)—Boost的序列化库的强大之处
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  首先,对于两个archive类我并不是很熟悉,这里摘其代码:

template<class Archive>
class interface_oarchive
{
protected:
    interface_oarchive(){};
public:
    /////////////////////////////////////////////////////////
    // archive public interface
    typedef mpl::bool_<false> is_loading;
    typedef mpl::bool_<true> is_saving;
 
    // return a pointer to the most derived class
    Archive * This(){
        return static_cast<Archive *>(this);
    }
 
    template<class T>
    const basic_pointer_oserializer *
    register_type(const T * = NULL){
        const basic_pointer_oserializer & bpos =
            boost::serialization::singleton<
                pointer_oserializer<Archive, T>
            >::get_const_instance();
        this->This()->register_basic_serializer(bpos.get_basic_serializer());
        return & bpos;
    }
 
    template<class T>
    Archive & operator<<(T & t){
        this->This()->save_override(t, 0);
        return * this->This();
    }
   
    // the & operator
    template<class T>
    Archive & operator&(T & t){
        #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
            return * this->This() << const_cast<const T &>(t);
        #else
            return * this->This() << t;
        #endif
    }
};

编缉推荐阅读以下文章

  • 序列化支持(4)—Boost的序列化库的强大之处
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  这个函数的实现很有意思,利用了模板参数,然后强转,最后通过子类一层一层的传递,导致这里强转实际得到的是最深层次子类的指针。技巧性非常强:)虽然我实际中从来没有用到过-_-!这里相当于父类在实现某些函数的时候,直接使用子类的函数实现。

    Archive * This(){
        return static_cast<Archive *>(this);

  就例子中

  oa << g;

   一句,其中函数来回调用,从子类到父类再到子类再到父类....#@$#@%#@相当的扭曲,利用的就是上面的This指针函数形式,为什么用这么多的 语句来实现本来一句简单的memcpy就能完成的功能,值得思考,也许要进一步对其整体的框架构造有所了解才行。但是这一点在文档中肯定是没有论述的,我 们寻找答案的唯一方法就是源代码了。

  先将其类互相之间的关系锊一下。

  text_oarchive继承自参数化类text_oarchive_impl,主要功能全由其提供。

   text_oarchive_impl参数化类继承自basic_text_oprimitive<std::ostream>与参数化类 basic_text_oarchive。text_oarchive_impl自身实现的代码主要是对各个字符串(包括 char*,wchar*,string,wstring)的序列化。

  text_oarchive_impl又是从参数化类basic_text_oprimitive和basic_text_oarchive继承过来。这里最好是画个UML图那就清晰了。但是由于我是如此的懒,所以我没有画-_-!

  其中basic_text_oprimitive的实现,告诉了我们,为什么需要This函数来调用子类的函数。

  通过跟踪源代码的boost::archive::text_oarchive oa(ofs);

   此句,会发现,最终ofs这个ofstream最终是传入到了这个参数化basic_text_oprimitive,并作为其引用成员变量os保存 的,然后basic_text_oprimitive C++的basic类型的save函数重载,而save函数实际的实现又都是通过os的 <<操作符来实现的。

编缉推荐阅读以下文章

  • 序列化支持(4)—Boost的序列化库的强大之处
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  这里最引人注目的就是&操作符的重载,使得使用起来非常方便,兼有使用Serialize函数和<<,>>操作符的两种方案的好处。

  我刚开始工作的时候以为序列化最大的好处就是对类的类成员变量与普通变量都使用了统一的处理接口,这自然也是序列化所需要的基本功能之一。

  可序列化的成员

  一下例子从文档衍生而来,经过添加必要代码使其可以执行

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#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()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
 
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;
public:
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
        latitude(lat_), longitude(long_){ }
        
    virtual ~bus_stop(){ }
};
 
 
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("busfile");
 
    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);
 
    bus_stop stop(latitude, longitude);
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << stop;
        // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    bus_stop newstop;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("busfile", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> newstop;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

编缉推荐阅读以下文章

  • 序列化支持(4)—Boost的序列化库的强大之处
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  这样,对于gps_positon这样的类成员变量,由于为其写过序列化函数了,就可以直接将其序列化

  在这里是用:

       ar & latitude;
       ar & longitude;

  的形式,这就是我刚开始唯一知道的序列化的好处。

  当然,对于派生的类,应该也能调用基类的序列化函数,这在C++中也应该属于序列化的基本功能。

  见下例:

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#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()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
 
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;
public:
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
        latitude(lat_), longitude(long_){ }
        
    virtual ~bus_stop(){ }
};
 
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;
 
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_)
    {}
 
    virtual std::string description() const
    {
       return street1 + " and " + street2;
    }
};
 
 
 
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_corner");
 
    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);
 
    bus_stop_corner stop_corner(latitude, longitude, "corn1", "corn2");
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << stop_corner;
       // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    bus_stop_corner new_stop_corner;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_corner", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_stop_corner;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

  可以尝试调试程序,这里为了简化代码,我没有将信息输出了。

   至此,我们已经有了序列化需要的基本功能了,至于其他更多复杂的结构不过就是此种方式的组合而已。另外,此种使用方式已经超过了我公司的序列化类 -_-!那个类实现是非常简单的,但是功能似乎也是如此的弱,但是这里的超过不过是通过&符号的重载来简化Serialize函数的调用而已,还 算不上什么质的飞跃。下面看质的飞跃所在。

 1.      非介入式版本

  感觉实际使用中我还没有碰到过,既然需要完全的public才能还原数据,那么介入不介入好像影响也不大了,除非碰到一个东西是别的公司写的,不让改,我是还没有碰到这样的情况。

  从这里开始见识Boost序列化库的强大。

  2.      指针的序列化:

  下面的例子为文档中例子的简化,并添加必要部分以方便运行及演示。

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#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()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
 
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;
public:
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
        latitude(lat_), longitude(long_){ }
        
    virtual ~bus_stop(){ }
};
 
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;
 
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_)
    {}
 
    virtual std::string description() const
    {
       return street1 + " and " + street2;
    }
};
 
 
class bus_route
{
    friend class boost::serialization::access;
    bus_stop_corner * stops[2];
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       int i;
       for(i = 0; i < 2; ++i)
           ar & stops[i];
    }
public:
    bus_route(bus_stop_corner *apStop1, bus_stop_corner *apStop2)
    {
       stops[0] = apStop1;
       stops[1] = apStop2;
    }
    bus_route(){}
};
 
 
 
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");
 
    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);
 
    bus_stop_corner *lpStop1 = new bus_stop_corner(latitude, longitude, "corn1", "corn2");
    bus_stop_corner *lpStop2 = new bus_stop_corner(latitude, longitude, "corn3", "corn4");
 
    bus_route route(lpStop1, lpStop2);
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
 
    delete lpStop1;
    delete lpStop2;
    return 0;
}

编缉推荐阅读以下文章

  • 序列化支持(3)—Boost的序列化库的使用
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  这里的强大之处在于指针反序列化的时候自动的分配了内存,这样简化了很多的操作,当然,这样就会存在文档中提出的内存泄露的问题,在此例 中的确存在,反序列化时分配了内存但是却没有合理的地方去释放,由外部去释放感觉并不是太妥当,boost文档中的建议是使用智能指针,比如 share_ptr。这个例子我们放到最后,先看看利用普通类的一条确保内存分配并不泄露的原则,哪里分配的哪里释放,对象自己管理自己的内存。

  见下例:(此例为文档中没有的)

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#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()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
 
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;
public:
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
        latitude(lat_), longitude(long_){ }
        
    virtual ~bus_stop(){ }
};
 
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;
 
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_)
    {}
 
    virtual std::string description() const
    {
       return street1 + " and " + street2;
    }
};
 
 
class bus_route
{
    friend class boost::serialization::access;
 
    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了
    bus_stop_corner * stops[2];
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       for(int i = 0; i < 2; ++i)
           ar & stops[i];
    }
public:
    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2)
    {
       stops[0] = new bus_stop_corner(aoStop1);
       stops[1] = new bus_stop_corner(aoStop2);
    }
 
    bus_route()
    {
       stops[0] = new bus_stop_corner;
       stops[1] = new bus_stop_corner;
    }
 
    ~bus_route()
    {
       for(int i = 0; i < 2; ++i)
       {
           delete stops[i];
       }
    }
};
 
 
 
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");
 
    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);
 
    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");
    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");
 
    bus_route route(loStop1, loStop2);
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

编缉推荐阅读以下文章

  • 序列化支持(3)—Boost的序列化库的使用
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  其实在一般情况下,只需要遵循了上述的原则,内存泄露问题一般不会存在,但是这里有个疑问就是,当指针分配了内存,boost序列化的时候是不是还是傻傻的去重新分配一次内存,然后导致第一次分配的内存没有正常释放,导致内存泄露呢?我们来检验一样。

  当new_route调用默认构造函数分配内存时,数组中指针的地址如下:

[0] = 0x003b7090 {street1="" street2="" }
[1] = 0x003bafb0 {street1="" street2="" }

  反序列化后:

[0] = 0x003b9150 {street1="corn1" street2="corn2" }
[1] = 0x003b9268 {street1="corn3" street2="corn4" }

   经证实。boost在指针已经分配过内存的情况下仍然重新为指针分配了一次内存,这一点够傻的,那么,这样傻的行为有没有一点保护呢?比如首先判断一下 指针是否为NULL,然后先delete再new呢?虽然这样的操作好像更傻。当时总好过与内存泄露,验证一下的方法很简单,在ia >> new_route;一句执行前在bus_stop_corner的析构函数上加断电,假如boost调用了delete,其析构一定会发生,事实是残酷 的。假如事先为指针分配了内存,那么必然发生内存的泄露。boost根本不管指针是否已经分配过内存,直接忽略,并重新分配内存。

  其实,一般而言,需要反序列化的时候,提供一个空的对象也是比较合理的,毕竟这是一个还原对象的过程,所以程序改成下面例子这样就可以在不使用智能指针的时候避免内存泄露了。

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#include <fstream>
 
// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <cstdlib>
using namespace std;
 
/////////////////////////////////////////////////////////////
// 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()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
 
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;
public:
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
        latitude(lat_), longitude(long_){ }
        
    virtual ~bus_stop(){ }
};
 
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;
 
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_)
    {}
 
    virtual std::string description() const
    {
       return street1 + " and " + street2;
    }
 
    ~bus_stop_corner()
    {
    }
};
 
 
class bus_route
{
    friend class boost::serialization::access;
 
    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了
    bus_stop_corner * stops[2];
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       for(int i = 0; i < 2; ++i)
           ar & stops[i];
    }
public:
    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2)
    {
       stops[0] = new bus_stop_corner(aoStop1);
       stops[1] = new bus_stop_corner(aoStop2);
    }
 
    bus_route()
    {
       stops[0] = NULL;
       stops[1] = NULL;
    }
 
    ~bus_route()
    {
       for(int i = 0; i < 2; ++i)
       {
           if(stops[i] != NULL)
           {
              delete stops[i];
           }
       }
    }
};
 
 
 
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");
 
    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);
 
    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");
    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");
 
    bus_route route(loStop1, loStop2);
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

编缉推荐阅读以下文章

  • 序列化支持(3)—Boost的序列化库的使用
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  这里的bus_route类有指针和内存分配但是没有合理的拷贝构造函数和operator=重载,仅仅只是作为演示使用,实际中这里几 乎是必须的,即使不需要使用到复制也应该将此两个函数放入private中以表示禁止复制,以防误用。(比如std的I/O stream类实现即是如此)

  改成上述例子中的形式后,需要注意的是反序列化前一定要是一个空对象,假如以前有分配内存的话需要提前释 放到,还好这些都可以很简单的由对象本身所保证。这一点可能的错误应用应该算是Boost为了易用性而导致的。。。。基本掌握了还算能接受,起码对于指针 的序列化还是简单了很多,仅仅是需要多注意一下传入的必须是空的指针就行。

  Boost作为准标准库,虽然有的时候显得有点庞大,但是 STL的搭配,和众多新Boost特性的搭配是非常的默契(不知道这个词是否恰当)。从上面的序列化就可以看出来,序列化是完全融入原有的C++ stream体系的,这点我们公司的序列化根本没有办法比。谈到这点就是想说,其实包括auto_ptr甚至shared_ptr在内的智能指针,包括 vector,map等STL容器,甚至连array在内。

  这里是使用智能指针的两个例子,但是boost的serialize库如此 偏心。share_ptr是内嵌在库里面的,而C++标准库的auto_ptr竟然没有内嵌在库里面,仅仅是在demo中给出实现。也就是说,明明实现 了,就是不想放到库里面去。如此不推荐使用auto_ptr的做法,完全见证了我当时强烈感叹的auto_ptr的异类。我就不知道他是怎么混进标准库 的。

  以下是智能指针的使用示例代码:

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#include <fstream>
 
// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <cstdlib>
#include <boost/tr1/memory.hpp>
#include <vector>
#include <map>
#include <boost/tr1/unordered_map.hpp>
#include <memory>
#include <boost/serialization/shared_ptr.hpp>
using namespace std;
using namespace boost;
using namespace boost::serialization;
 
#include <boost/serialization/split_free.hpp>
 
namespace boost {
    namespace serialization {
 
       /////////////////////////////////////////////////////////////
       // implement serialization for auto_ptr<T>
       // note: this must be added to the boost namespace in order to
       // be called by the library
       template<class Archive, class T>
       inline void save(
           Archive & ar,
           const std::auto_ptr<T> &t,
           const unsigned int file_version
           ){
              // only the raw pointer has to be saved
              // the ref count is rebuilt automatically on load
              const T * const tx = t.get();
              ar << tx;
       }
 
       template<class Archive, class T>
       inline void load(
           Archive & ar,
           std::auto_ptr<T> &t,
           const unsigned int file_version
           ){
              T *pTarget;
              ar >> pTarget;
              // note that the reset automagically maintains the reference count
#if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1)
              t.release();
              t = std::auto_ptr<T>(pTarget);
#else
              t.reset(pTarget);
#endif
       }
 
       // split non-intrusive serialization function member into separate
       // non intrusive save/load member functions
       template<class Archive, class T>
       inline void serialize(
           Archive & ar,
           std::auto_ptr<T> &t,
           const unsigned int file_version
           ){
               boost::serialization::split_free(ar, t, file_version);
       }
 
    } // namespace serialization
} // namespace boost
 
 
/////////////////////////////////////////////////////////////
// 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()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
 
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;
public:
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
        latitude(lat_), longitude(long_){ }
        
    virtual ~bus_stop(){ }
};
 
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;
 
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_)
    {}
 
    virtual std::string description() const
    {
       return street1 + " and " + street2;
    }
 
    ~bus_stop_corner()
    {
    }
};
 
 
class bus_route
{
    friend class boost::serialization::access;
 
    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了
    shared_ptr<bus_stop_corner> msptrBusStop;
    auto_ptr<bus_stop_corner> maptrBusStop;
 
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & msptrBusStop;
       ar & maptrBusStop;
    }
public:
    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2):
      msptrBusStop(new bus_stop_corner(aoStop1)),
       maptrBusStop(new bus_stop_corner(aoStop2))
    {
    }
 
    bus_route()
    {
 
    }
 
    ~bus_route()
    {
    }
};
 
 
 
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");
 
    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);
 
    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");
    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");
 
    bus_route route(loStop1, loStop2);
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

编缉推荐阅读以下文章

  • 序列化支持(3)—Boost的序列化库的使用
  • 序列化支持(2)—Boost的序列化库
  • 序列化支持(1)—前言

  至于剩下的STL容器之类的,由于和普通的成员变量都看不出区别了,我弄个简单的示例说明一下就好了,按原文档的话来说是:

  The serialization library contains code for serialization of all STL classes.

  还不够吗?

   从上面的例子上已经可以看出std::string肯定没有问题了,我只想知道一个东西,tr1的unordered_map实现了没有。但是非常遗 憾。呵呵,没有实现,不过vector和list的实现自然是没有问题,一下给出例子,这里需要说明的是,对于STL来说,可能是考虑到每个实现都比较 大,所以在使用了相应的容器后,需要包含序列化相应的头文件,比如vector就是boost/serialization/vector.hpp,依次 类推,这点share_ptr的示例其实就已经使用了,但是没有特别说明。

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#include <fstream>
 
// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <cstdlib>
#include <boost/tr1/memory.hpp>
#include <vector>
#include <map>
#include <memory>
#include <boost/serialization/vector.hpp>
using namespace std;
using namespace boost;
using namespace boost::serialization;
 
class CSerializeAble
{
public:
    std::vector<int> miVec;
 
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & miVec;
    }
};
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("contains");
 
    CSerializeAble loSA;
    loSA.miVec.push_back(1);
    loSA.miVec.push_back(2);
    loSA.miVec.push_back(3);
 
    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << loSA;
       // 在调用析构函数时将关闭存档和流
    }
 
    // ... 晚些时候,将类实例恢复到原来的状态
    CSerializeAble lonewSA;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("contains", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> lonewSA;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

  到了这里,已经可以看到Boost::Serialize的强大了,这里还想说明的是,虽然文档中有std::list<bus_stop *> stops;这样的例子,但是实际上我这样做却无法编译通过。这点很奇怪

   然后,Boost::Serialize由于是完全融入C++的IOStream系统的,所以,只要你实现你自己的Stream,比如文档中提到的 XML系统,你就可以实现任意的方式保存你序列化的数据。这样的开放性也是值得一提的,光是这一点都不是一般的非“准标准库”可以比拟的。序列化大概就讲 这么多吧。序列化在公司只用了半天去了解,但是自己这段时间工作太忙,结果学习Boost的序列化库的使用,还不是看其实现,就用了这么久,真是无奈。

posted on 2010-12-26 20:35  AlexRowe  阅读(7421)  评论(0编辑  收藏  举报

导航