您的位置:首页 > 编程语言 > C语言/C++

C++对象序列化方案对比

2014-01-23 12:22 211 查看
序列化是将对象状态信息转换为可存储或传输的过程,序列化时,对象会将当前状态写入到临时或持久性的存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

对象序列化反序列化通常用于:

1. 将对象存储于硬盘上

2. 在网络上传送对象的字节序列

更多介绍

常见的C++序列化方案

==Boost.Serialization==

介绍:Boost.Serialization可以创建或重建程序中的等效结构,并保存为二进制数据、文本数据、XML或者有用户自定义的其他文件。该库具有以下吸引人的特性:

1. 代码可移植(实现仅依赖于ANSI C++)

2. 深度指针保存与恢复

3. 可以序列化STL容器和其他常用模版库

4. 数据可移植

5. 非入侵性

使用教程

下载Boost库,并根据需要编译(Boost库涉及较广)。

编写应用。

●依需求编写入侵式或非入侵式序列化方法

●支持STL容器类、指针、父子类的序列化

为需要序列化的类添加序列化代码。

●初始化fstream

●需求初始化xml, text, binary存档

●写入对象,并关闭文件流

== MFC Serialization ==

简介:Windows平台下可使用MFC中的序列化方法。MFC 对 CObject 类中的序列化提供内置支持。因此,所有从 CObject 派生的类都可利用 CObject 的序列化协议。(MSDN中的介绍

使用

为VS项目添加MFC支持

●设置项目属性

●包含头文件C++:

1 #include <afxwin.h>

2 #include <afxtempl.h>

●编写继承CObject的类

●实现序列化方法C++:

1 void Serialize(CArchive& ar);

●添加序列化宏C++:

1 //添加在声明类中

2 DECLARE_SERIAL(basic_pojo_mfc)

3 //…

4 //添加在实现文件中

5 IMPLEMENT_SERIAL(MyObject, CObject, 1)

●编写序列化与反序列化的对象

●创建CFile,CArchive对象

●写入对象,关闭资源

==Google Protocol Buffers==

简介:Google Protocol Buffers (GPB)是Google内部是用的数据编码方式,旨在用来代替XML进行数据交换。可用于数据序列化与反序列化。主要特性有:

1. 高效

2. 语言中立(Cpp, Java, Python)

3. 可扩展

官方文档

使用

●下载GPB,并编译出需要使用的库。

●编写.proto文件,并编译出.cc与.h文件。

●依规则编写.proto

●编译

●Shell/CMD:

1 protoc -I=$SRC_DIR –cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

●编写序列化与反序列化代码。

比较

==测试用例介绍==

比较维度

●序列化与反序列化消耗的时间

●产生数据文件大小

测试数据类型

C++:01 //基本数据类型

02 class basic_pojo {

03 public :

04 char char8;

05 unsigned char uchar8;

06 short short16;

07 unsigned short ushort16;

08 int int32;

09 unsigned int uint32;

10 long long32;

11 unsigned long ulong32;

12 float float32;

13 double double64;

14 bool bool8;

15 };

16

17 //复合数据类型

18 class complex_pojo {

19 public :

20 string string_stl;

21 basic_pojo_boost basic_class;

22 };

测试代码

结果

序列化与反序列化消耗的时间:





产生数据文件大小:



数据:
Protocol BuffersBoost.SerializationMFC:SerializationSerialization(ms)942191218Unserialization(ms)2032961282Archive Size(KB)459044935372

结论

Google Protocol Buffers效率较高,但是数据对象必须预先定义,并使用protoc编译,适合要求效率,允许自定义类型的内部场合使用。Boost.Serialization 使用灵活简单,而且支持标准C++容器。相比而言,MFC的效率较低,但是结合MSVS平台使用最为方便。希望有时间补充更多的序列化方案及测试指标。

boost例子:

对于核心数据相对比较集中的应用程序来说,serialization机制可以直接充当文档保存与打开功能的实现工具,这也是很多成熟的应用程序framework都提供serialization支持的原因

但是个人认为,serialization最精彩的用处在于保存现场,比如在探索性的科研应用程序开发过程中,很可能一部分算法已经固定下来,其余的有待进一步探索,而确定下来的部分有可能十分time consuming,如果每次改一下算法都要从头计算,就会很费时间,长期下去对工作情绪会有很大影响,这时候可以用serialization把每一步的结果存成文件,下次启动时任选一个开始新的计算。

Boost的Serialization库是一个十分强大的工具,它文档中提到的11个开发目标列举如下:

1.代码移植性,只依赖于ANSI C++标准

2.代码简洁性,的确运用Boost::Serialization所需要的代码量很小

3.每个类有自己独立的版本控制,以保证旧的save结果可以被新的程序load回来

4.深度指针save与load,不仅serialize指针本身,而且包括它指向的对象数据

5.多个指针指向同一个对象不会被serialize多次

6.对常用STL容器的支持

7.串行数据的平台移植性

8.类如何被串行化与串行数据按何种格式存储相互无关

9.非侵入性,这对于数据中使用了第三方类库的情形很有效

最后两个没有理解,希望高人指点

10.The archive interface must be simple enough to easily permit creation of a new type of archive.

11.The archive interface must be rich enough to permit the creation of an archive that presents serialized data as XML in a useful manner.

下面是我试用Boost::Serialization的记录

第一个例子

假设有这样一个类需要串行化


class Data


{


public :


int mInt;


} ;

这里为了简化起见,成员变量都设成public了,串行化的代码如下

1

#include < fstream >

2


3

// fewest include headers

4

#include < boost / archive / text_iarchive.hpp >

5

#include < boost / archive / text_oarchive.hpp >

6


7

// use this to ease the archive selection

8

typedef boost::archive::text_iarchive iarchive;

9

typedef boost::archive::text_oarchive oarchive;

10


11

class Data

12

{

13

friend class boost::serialization::access;

14


15

protected :

16


17

template < class Archive >

18

void serialize(Archive & ar, const unsigned int /* file_version */ )

19

{

20

ar & mInt;

21

}

22


23

public :

24


25

int mInt;

26


27

static void saveData( const Data & obj, std:: string fileName)

28

{

29

std::ofstream ofs(fileName.c_str());

30

oarchive oa(ofs);

31

oa << obj;

32

}

33


34

static void loadData(Data & obj, std:: string fileName)

35

{

36

std::ifstream ifs(fileName.c_str());

37

iarchive ia(ifs);

38

ia >> obj;

39

}

40

} ;

41


42

void main()

43

{

44

Data d1;

45

d1.mInt = 3 ;

46


47

Data::saveData(d1, " output.txt " );

48


49

Data d2;

50

Data::loadData(d2, " output.txt " );

51


52

// results should be the same.

53

ToolLib::LOG(TOSTR(d2.mInt));

54

}

成员serialize函数是定义类如何被串行化之规则的核心。

由于使用了RTTI机制,serialize函数不需要为virtual,永远只要是void就可以,在串行化指针的时候能够被正确调用。

serialize函数中的 & 运算符在load时调用 >>,而在save时调用 <<,这样save和load只要一个函数就可以。

saveData和loadData函数必不可少,由于serialization库强大的编译检查机制,如果不用这样的方式来save一个对象,往往会因为这个对象不是const而编译失败。

所用的archive类型可以任选,如text或binary,上例中的typedef即是为了封装这一变化

非侵入性

下面一例演示了串行化不可侵入的类型。这里用的是 WildMagicLib2.5中的 2-Vector


#include < WildMagic2p5 / Include / WmlVector2.h >




class Data


{


friend class boost::serialization::access;




protected :




template < class Archive >


void serialize(Archive & ar, const unsigned int /* file_version */ )


{


ar & mVec2d;


}




public :




Wml::Vector2d mVec2d;




static void saveData( const Data & obj, std:: string fileName);


static void loadData(Data & obj, std:: string fileName);


} ;




namespace boost {


namespace serialization {


template < class Archive, class Real >


void serialize(Archive & ar, Wml::Vector2 < Real >& g, const unsigned int version)


{


ar & g.X();


ar & g.Y();


}


} // namespace serialization


} // namespace boost



这种情况下,需要这个全局serialize在能够访问到那个类里需要串行化的数据,常常load和save的方法不一样,如load时调用setVar,save时调用getVar,这时Archive::is_loading和Archive::is_saving常数就有用了。一种等价但是更直观的方法是使用BOOST_SERIALIZATION_SPLIT_MEMBER或者BOOST_SERIALIZATION_SPLIT_FREE宏,两者分别生成调用load/save成员函数和load/save全局函数的代码。

在serialization内部,是通过定义全局serialize函数模板,并在里面调用成员serialize函数来实现的,如下所示,需要非侵入的对象只要特化这个全局函数就可以了。


// default implemenation - call the member function "serialize"


template < class Archive, class T >


inline void serialize(


Archive & ar, T & t, const BOOST_PFTO unsigned int file_version


) {


access::serialize(ar, t, static_cast < unsigned int > (file_version));


}

STL容器支持

通过包含一些serialization提供的头文件,对STL容器可以像普通变量一样支持


// STL support headers


#include < boost / serialization / vector.hpp >


#include < boost / serialization / string .hpp >




class Data


{


friend class boost::serialization::access;




protected :




template < class Archive >


void serialize(Archive & ar, const unsigned int /* file_version */ )


{


ar & mStr;


ar & mVecInt;


ar & mVecStr;


}




public :




std:: string mStr;


std::vector < int > mVecInt;


std::vector < std:: string > mVecStr;




static void saveData( const Data & obj, std:: string fileName);


static void loadData(Data & obj, std:: string fileName);


} ;

指针与数组


class ClassA


{


public :


int mInt;


} ;




class Data


{


friend class boost::serialization::access;




protected :




template < class Archive >


void serialize(Archive & ar, const unsigned int /* file_version */ )


{


ar & mPtrInt;


ar & mArrInt;


ar & mPtrData;


ar & mPtrA;


}




public :




Data():mPtrData(NULL), mPtrInt(NULL), mPtrA(NULL) {}




Data * mPtrData;


int mArrInt[ 10 ];


int * mPtrInt;


ClassA * mPtrA;




static void saveData( const Data & obj, std:: string fileName);


static void loadData(Data & obj, std:: string fileName);


} ;




namespace boost {


namespace serialization {


template < class Archive >


void serialize(Archive & ar, int & g, const unsigned int version)


{


ar & g;


}


} // namespace serialization


} // namespace boost



数组可以直接串行化,指针比须保证有效,所以必须保证在串行化之前经过初始化。

对于基本类型如int,可以直接串行化,但其指针int*,要当作不可侵入类型的指针来看待,所以需要一个全局serialize函数来说明int类型的串行化方式

对于有基类指针的串行化,代码如下


class ClassA


{


public :


int mIntA;




virtual void someMethod() = NULL;




template < class Archive >


void serialize(Archive & ar, const unsigned int /* file_version */ )


{


ar & mIntB;


}


} ;




BOOST_IS_ABSTRACT(ClassA)




class ClassB: public ClassA


{


public :


int mIntB;




virtual void someMethod() {}




template < class Archive >


void serialize(Archive & ar, const unsigned int /* file_version */ )


{


ar & boost::serialization::base_object < ClassA > ( * this );


ar & mIntB;


}


} ;




BOOST_CLASS_EXPORT(ClassB)

纯虚类后加上BOOST_ISABSTRACT,而可能会被串行化到的子类用BOOST_CLASS_EXPORT,这样就可以在任何地方串行化 ClassA* 的成员变量。

子类的serialize函数里必须要照顾到基类的成员。

版本控制

在serialize函数中的version参数就是用于版本控制的,所有类的版本号默认为0,新版本的类可以自己指定版本号以便与旧版本相区别。如下

BOOST_CLASS_VERSION(ClassA, 1)

对于save过程,版本号始终为新的,而load过程取决于文件中保存的值,对于新版本新增变量的情况可以这样解决


// old definition


class ClassA


{


public :


int mInt;


} ;




// new definition


class ClassA


{


public :


int mInt;


int mIntNew;




template < class Archive >


void serialize(Archive & ar, const unsigned int ver)


{


ar & mInt;




if (ver == 1 )


ar & mIntB;


}


} ;




BOOST_CLASS_VERSION(ClassA, 1 )

对于有改动的情形,稍微复杂一点,可以这样


// old definition


class ClassA


{


public :


TypeA mVarA;


} ;




// new definition


class ClassA


{


public :




// suppose now we use B and C instead of A


TypeB mVarB;


TypeC mVarC;




template < class Archive >


void serialize(Archive & ar, const unsigned int ver)


{


if (ver < 1 )


{


// here must be loading




TypeA varA;


ar & varA;




// now derive mVarB & mVarC from varA;


}


else


{


ar & mVarB;


ar & mVarC;


}


}


} ;




BOOST_CLASS_VERSION(ClassA, 1 )

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: