老所工作室 : boost::serialization库之对象状态的保存

来源:互联网 发布:淘宝网魅族 编辑:程序博客网 时间:2024/05/17 01:42
本文转自

http://blog.ipattern.org/archives/18

关于对象的序列化,是OO语言中的一个重要内容。然而,作为C++语言的标准库,STL并没有提供对序列化的支持。还好,我们还有boost这个“准标准”库,它倒是提供了序列化的支持。让我们来看看它都有些什么能耐吧。



首先,我们定义一个用于测试的类:


#include 
#include

using namespace std;

class log_entry
{
int _count;
public:
log_entry() : _count(0) {};
const int& count() const { return _count; };
void count_inc() { _count++; };
};

我们创建一个日志条目类,里面包含一个计数器。我们计划通过保存该对象不同时刻的状态,来组成一个连续的日志。换句话说,我们希望用一系列不同状态的日志条目对象,来组成一个完整的日志。


要使用boost::serialization库来保存对象,我们还需要添加方法,使得该类是“可序列化的”。boost库提供两种方式来使对象可序列化:在类内部添加序列化接口,或者不改变类的定义,添加全局的针对该类的序列化函数。这里,我们采取第一种方法。关于第二种方法,可以参考其在线的教程。


#include 
#include

#include

using namespace std;

class log_entry
{
friend class boost::serialization::access;
int _count;
template
void serialize(Archive& ar, const unsigned int version)
{
ar & _count;
};

public:
log_entry() : _count(0) {};
const int& count() const { return _count; };
void count_inc() { _count++; };
};

首先,需要为boost::serialization::access授权访问该类的私有成员,其次添加一个标准的serialize函数。这个函数将保存/读取融合到了一个函数,当保存时,Archive的&操作就是保存,反之则是读取。第二个参数是一个用于归档的版本管理,我们这里暂时先不考虑。


那么对于实际操作,我们的步骤就是,先构造一个归档对象,然后用<<>>操作符来保存和读取对象的状态:


int main(int, char**)
{
// create archive, we use string stream, instead of file stream
ostringstream so;
boost::archive::text_oarchive ao(so);

// now change object state, and snapshot it into archive
log_entry entry;

entry.count_inc();

ao << entry;

entry.count_inc();
entry.count_inc();

ao << entry;

// now we freeze serialized string, and use it create input
// archive.
istringstream si(so.str());
boost::archive::text_iarchive ai(si);

// we use another object, to restore object
log_entry entry_2;

ai >> entry_2;
cout << "1st state is: " << entry_2.count() << endl;

ai >> entry_2;
cout << "2nd state is: " << entry_2.count() << endl;

return 0;
}

注意,我们引入了text类型的输入和输出archive,所以需要在头文件区域加上:


#include 
#include

这段代码保存了两个状态,希望第一个状态输出1,第二个状态输出3。代码的逻辑很简单,想必也没有错了,那么我们用以下命令来进行编译:(如果没有安装boost_serialization清根据具体操作系统进行安装)


] g++ -o test -lboost_serialization test.cpp

你一定很惊讶,这么简单的代码,怎么也会出错:


soloman@ninja-ubuntu:~/Temp$ g++ -o test -lboost_serialization test.cpp

/usr/include/boost/archive/detail/oserializer.hpp: In function
‘void boost::archive::save(Archive&, T&) [with Archive =
boost::archive::text_oarchive, T = log_entry]’:

/usr/include/boost/archive/detail/common_oarchive.hpp:62: instantiated
from ‘void
boost::archive::detail::common_oarchive::save_override(T&,
int) [with T = log_entry, Archive =
boost::archive::text_oarchive]’

/usr/include/boost/archive/basic_text_oarchive.hpp:75: instantiated
from ‘void
boost::archive::basic_text_oarchive::save_override(T&,
int) [with T = log_entry, Archive =
boost::archive::text_oarchive]’

/usr/include/boost/archive/detail/interface_oarchive.hpp:79:
instantiated from ‘Archive&
boost::archive::detail::interface_oarchive::operator<<(T&)
[with T = log_entry, Archive = boost::archive::text_oarchive]’

test.cpp:36: instantiated from here

/usr/include/boost/archive/detail/oserializer.hpp:566: 错误:
‘sizeof’ 不能用于不完全的类型
‘boost::STATIC_ASSERTION_FAILURE


而且,这个错误还非常奇怪。其实,库的作者早就料定我们会碰到这个错误了,在在线文档中,专门有一节文字用来解释这个“编译时的陷阱”。


原来,这个序列化库有个功能,是对象的跟踪,也就是保证同一个对象在序列化里面只有一个实例,而其他对该对象的引用则直接序列化一个对已存在对象的
指针引用。那么在我们这段代码里,我们对对象entry序列化了两次,而序列化库用来判断两个对象是否是同一对象的标志就是其指针。我们的entry对象
是声明在栈上的,所以两次序列化时,其指针是相同的。serialization库认为你这样做是不符合逻辑的,所以通过编译时的错误来终止错误的继续。


但是,这个逻辑对我们来说确实正确的,我们并不想为每个状态都新产生一个对象,我们实际上是想“冻结”该对象在各个时刻时
的状态,并在序列化流里保存许多该状态时对象的副本。在编译器报错后,我们不得不再次慎重地考虑了一下我们的逻辑,确认无误后,接下来就是要怎么告诉编译
器:我确保,我的逻辑是正确的,一切后果我自负!


有两个方法,一是关闭对象跟踪,二是通过const cast添加一个语意来确保我们的逻辑。第二个方法其实就是显式地进行对象状态“冻结”,很多时候,我们应该选择“显式”,避免”隐式“:


int main(int, char**)
{
// create archive, we use string stream, instead of file stream
ostringstream so;
boost::archive::text_oarchive ao(so);

// now change object state, and snapshot it into archive
log_entry entry;

// create object freeze!
const log_entry& entry_freeze = const_cast(entry);


entry.count_inc();

ao << entry_freeze;

entry.count_inc();
entry.count_inc();

ao << entry_freeze;

// now we freeze serialized string, and use it create input
// archive.
istringstream si(so.str());
boost::archive::text_iarchive ai(si);

// we use another object, to restore object
log_entry entry_2;

ai >> entry_2;
cout << "1st state is: " << entry_2.count() << endl;

ai >> entry_2;
cout << "2nd state is: " << entry_2.count() << endl;

return 0;
}

这样,一个const_cast代表我们显式地同意打破对象跟踪原则,便于代码逻辑的表达。这样修改后,编译通过,我们获得了正确的输出:


soloman@ninja-ubuntu:~/Temp$ ./test
1st state is: 1
2nd state is: 3

当然,有兴趣的话,我们也可以添加代码将序列化的字符串打印出来瞧瞧。


下面是完整的测试代码test.cpp:


#include 
#include

#include
#include

#include

using namespace std;

class log_entry
{
friend class boost::serialization::access;
int _count;
template
void serialize(Archive& ar, const unsigned int version)
{
ar & _count;
};
public:
log_entry() : _count(0) {};
const int& count() const { return _count; };
void count_inc() { _count++; };
};

int main(int, char**)
{
// create archive, we use string stream, instead of file stream
ostringstream so;
boost::archive::text_oarchive ao(so);

// now change object state, and snapshot it into archive
log_entry entry;

// create object freeze!
const log_entry& entry_freeze = const_cast(entry);

entry.count_inc();

ao << entry_freeze;

entry.count_inc();
entry.count_inc();

ao << entry_freeze;

// what's inside serialized string?
cout << "==== serialized string ====" << endl;
cout << so.str() << endl;
cout << "===========================" << endl;

// now we freeze serialized string, and use it create input
// archive.
istringstream si(so.str());
boost::archive::text_iarchive ai(si);

// we use another object, to restore object
log_entry entry_2;

ai >> entry_2;
cout << "1st state is: " << entry_2.count() << endl;

ai >> entry_2;
cout << "2nd state is: " << entry_2.count() << endl;

return 0;
}

附带输出序列化字符串的输出为:


soloman@ninja-ubuntu:~/Temp$ ./test
==== serialized string ====
22 serialization::archive 4 0 0 1 3
===========================
1st state is: 1
2nd state is: 3




原创粉丝点击