序列化(serializtion)是指把对象信息转换成可以存储或者通过网络连接传输格式的过程,然后在本地或者在另一个计算机环境中可以重构出原有的对象信息是指反序列化。
前言
本文只介绍通过boost serialization存储的优势,先通过例子简单明白。假如现在你是一个学校学生系统管理员,你需要保存学生信息,比如学生学好,名字,年龄。当然最简单的方式是通过C++的文本操作解决方案。
//***********************************************************
//FUNCTION::
void CStudentInfoUtility::writeStudentInfo(const std::string vFileName, const CStudent* vStudent) const
{
_ASSERT(!vFileName.empty() && vStudent);
std::ofstream FileOut;
FileOut.open(vFileName, std::ios::binary);
_HIVE_EARLY_EXIT(FileOut.fail(), "File open fail");
FileOut << vStudent->getStudentId() << " ";
FileOut << vStudent->getStudentName() << " ";
FileOut << vStudent->getStudentAge() << std::endl;
FileOut.close();
}
//***********************************************************
//FUNCTION::
void CStudentInfoUtility::readStudentInfo(const std::string vFileName, CStudent* vStudent) const
{
_ASSERT(!vFileName.empty() && vStudent);
std::ifstream FileIn;
FileIn.open(vFileName, std::ios::binary);
_HIVE_EARLY_EXIT(FileIn.fail(), "File open fail");
int StudentId = 0, Age = 0;
std::string Name;
FileIn >> StudentId;
FileIn >> Name;
FileIn >> Age;
vStudent->setStudentId(StudentId);
vStudent->setStudentName(Name);
vStudent->setStudentAge(Age);
FileIn.close();
}
这样可以满足需求,但是如果现在需要添加学生新的信息,那么我需要改动上面的代码,这违背了面向对象的开闭原则,而且最重要的是上面的操作非常繁琐,都需要自己自定义去写和读相应属性操作。然而boost serialization很好的帮我们做了这些工作,它的优势是简单和高效,下面我依次介绍boost serialization的用法。
Boost Serialization Classification
Boost serialization分为两类,一个是侵入式(Intrusive),即嵌入代码到类中。
class CPerson
{
public:
CPerson(void);
CPerson(const std::string& vName, unsigned int vAge);
virtual ~CPerson(void);
const std::string& getName() const {return m_Name;}
int getAge() const {return m_Age;}
private:
std::string m_Name;
unsigned int m_Age;
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & m_Name;
ar & m_Age;
}
};
第二类是非侵入式,它的特点是没有暴露类的定义,不过需要暴露类的成员变量,即private需要改动未public,因为boost::serialization需要访问成员变量,所以需要暴露。但是我不建议这种写法,违背了面向对象的封装。
class CNonIntrusive
{
public:
CNonIntrusive(void);
CNonIntrusive(const std::string& vName, unsigned int vAge);
virtual ~CNonIntrusive(void);
const std::string& getName() const {return m_Name;}
int getAge() const {return m_Age;}
std::string m_Name;
unsigned int m_Age;
};
namespace boost {
namespace serialization {
template<class Archive>
void serialize(Archive & ar, CNonIntrusive & g, const unsigned int version)
{
ar & g.m_Name;
ar & g.m_Age;
}
}
}
Serializable Members
序列化成员变量,如同上面侵入式的代码,不过首先需要加上头文件#include <boost/serialization/serialization.hpp>
类中写法如侵入式代码。
在成员变量下声明了友元类access,表示可以访问到CPerson这个类的私有变量,接着下部有个模板函数serialize 其中的Archive& ar可以对成员变量进行序列化和反序列,当序列化的时候,&表示 <<。而反序列化的时候就表示 >>。
这里使用的是二进制的文档序列化,OS当成OA构造函数形参传入, OA << Person1
会调用刚刚的模板函数serialize,然后模板函数会把成员变量值传入OA中的OS中。
Derived Classes
序列化子类,只需要在子类加上ar & boost::serialization::base_object<CPerson>(*this);
表示会调用父类的serialize序列化父类的成员变量。
#pragma once
#include "person.h"
#include <boost/serialization/export.hpp>
class CPersonChild : public CPerson
{
public:
CPersonChild(void);
CPersonChild(const std::string& vName, unsigned int vAge, unsigned int vChildTag);
virtual ~CPersonChild(void);
unsigned int getChildTag() const {return m_ChildTag;}
private:
unsigned int m_ChildTag;
friend boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & boost::serialization::base_object<CPerson>(*this);
ar & m_ChildTag;
}
};
Pointers
其中需要注意的是在反序列化的时候CStudent* pSecondStudent = nullptr;
这里不能new一个对象,需要赋空值,因为boost::serialize模板函数会判断是否存在当前对象,如果没有它会创建一个对象,而如果你new了一个对象,就存在两个对象,就会存在内存泄漏。
Arrays
STL Collections
注意序列化stl 容器需要包含相应的头文件,这里是vector,所以头文件是#include <boost/serialization/vector.hpp>
。
Class Versioning
void serialize(Archive& ar, const unsigned int version)
刚刚没说第二个形参的作用,它表示的是版本号,默认是0。我们可以手动设置当前类对象序列的版本号来控制序列化相应属性。比如,你以前序列化了一个类,现在这个类加了新属性,那么反序列以前的文档肯定出错,因为文档内容是没有相应的新属性,所以可以使用后面的version来控制是否反序列化。
#pragma once
#include <string>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/version.hpp>
class CVersion
{
public:
CVersion(void);
CVersion(const std::string& vName, unsigned int vAge);
virtual ~CVersion(void);
const std::string& getName() const {return m_Name;}
int getAge() const {return m_Age;}
private:
unsigned int m_Age;
std::string m_Name;
friend class boost::serialization::access;
template <class Archive>
friend void serialize(Archive &ar, CVersion &v, const unsigned int version);
//template<class Archive>
//void serialize(Archive& ar, const unsigned int version)
//{
// ar & m_Age;
// if (version > 0)
// ar & m_Name;
//}
};
BOOST_CLASS_VERSION(CVersion, 1)
template <typename Archive>
void serialize(Archive &ar, CVersion& v, const unsigned int version)
{
ar & v.m_Age;
if (version > 0)
ar & v.m_Name;
}
BOOST_CLASS_VERSION(CVersion, 1)
表示当前序列化的版本是1,从而也序列化名字。
Polymorphism
面向对象的第三大特性多态是否也支持呢。答案是肯定的,而且有两种方式支持多态。
- export
头文件#include <boost/serialization/export.hpp>
//BOOST_CLASS_EXPORT(CPersonChild)
BOOST_CLASS_EXPORT(CPersonChild, "CPersonChild")
//***********************************************************
//FUNCTION::
void testPolymorphism()
{
std::ofstream FileOut("archive.txt");
boost::archive::text_oarchive OA(FileOut);
CPerson* pPerson = new CPersonChild("Polymorphism", 10, 1);
OA << pPerson;
FileOut.close();
delete pPerson;
CPerson* pPerson2 = nullptr;
std::ifstream FileIn("archive.txt");
boost::archive::text_iarchive IA(FileIn);
IA >> pPerson2;
CPersonChild* pPerson3 = dynamic_cast<CPersonChild*>(pPerson2);
std::cout << pPerson3->getName() << " " << pPerson3->getAge() << " " << pPerson3->getChildTag() << std::endl;
}
BOOST_CLASS_EXPORT(CPersonChild, "CPersonChild")
注意这个标签,相当于给派生类指定一个key,不过直接使用BOOST_CLASS_EXPORT(CPersonChild)
也是可以的,都能找到相应的派生类。
- registration
//***********************************************************
//FUNCTION::
void testPolymorphism2()
{
std::ofstream FileOut("archive.txt");
boost::archive::text_oarchive OA(FileOut);
OA.register_type<CPersonChild>();
CPerson* pPerson = new CPersonChild("Polymorphism 2", 10, 1);
OA << pPerson;
FileOut.close();
delete pPerson;
CPerson* pPerson2 = nullptr;
std::ifstream FileIn("archive.txt");
boost::archive::text_iarchive IA(FileIn);
IA.register_type<CPersonChild>();
IA >> pPerson2;
CPersonChild* pPerson3 = dynamic_cast<CPersonChild*>(pPerson2);
std::cout << pPerson3->getName() << " " << pPerson3->getAge() << " " << pPerson3->getChildTag() << std::endl;
}
OA.register_type<CPersonChild>();
注册了派生类的信息,所以也能根据相应的子类进行序列化和反序列化。
Splitting serialize into save/load
上面的序列化和反序列化都是通过一个模板函数serialize函数,其实这个函数可以拆分,这样在特定的场景也能功能明确。比如刚刚的版本控制,也能通过拆分函数来完成。
#pragma once
#include "Person.h"
#include <boost/serialization/list.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/split_member.hpp>
class CSplitting
{
public:
CSplitting(void){}
CSplitting (const std::string& vName) : m_SplittingName(vName){}
~CSplitting (void);
void addPerson(CPerson* vPerson) {_ASSERT(vPerson); m_PersonList.push_back(vPerson);}
void displayInfo();
private:
std::string m_SplittingName;
std::list<CPerson*> m_PersonList;
friend class boost::serialization::access;
template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
ar & m_SplittingName;
ar & m_PersonList;
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
if(version > 0)
ar & m_SplittingName;
ar & m_PersonList;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
};
BOOST_CLASS_VERSION(CSplitting, 1)
序列化调用save函数,反序列化调用load函数。注意类中需要加入一个重要的宏,BOOST_SERIALIZATION_SPLIT_MEMBER
。
【参考资料】
【1】http://blog.csdn.net/zj510/article/details/8105408
【2】http://zh.highscore.de/cpp/boost/serialization.html
【3】http://www.ibm.com/developerworks/cn/aix/library/au-boostserialization/index.html
【4】 http://www.boost.org/doc/libs/1_51_0/libs/serialization/doc/index.html