序列化是一个常见的功能,与数据库、配置文件不同,序列化一般是以二进制方式存储的,不考虑肉眼可读性。
序列化的基本原理很简单,用同样的顺序写入写出即可。主要需要琢磨的是如何用尽可能简洁的方式来实现,客户代码需要添加的部分越少越好。
这是我写过的一个版本(测试代码也在里面):
//Archive.h 二进制序列化
#ifndef STD_ARCHIVE_H
#define STD_ARCHIVE_H
namespace ns__std_2
{
class CArchive
{
public:
CBuffer m_buf;
void StartArchive(char const * name)
{
m_buf.setSize(0);
m_buf.AddData((void *)name,strlen(name)+1);
}
//对各种类型的操作
template
CArchive & operator<<(T const & data)
{
data.Serialize(*this);
return *this;
}
template
CArchive & Add(T const & data)
{
m_buf.AddData(&data,sizeof(T));
return *this;
}
//对基本类型的特化,如果用到其它基本类型会导致“Serialize不支持”错误,需要在此增加
template
CArchive & operator<<(sstring const & data) { m_buf.AddData(data.c_str(), data.size() + 1); return *this; }
CArchive & operator<<(char const * data) { m_buf.AddData(data, strlen(data) + 1); return *this; }
CArchive & operator<<(char * data) { m_buf.AddData(data, strlen(data) + 1); return *this; }
CArchive & operator<<(string const & data){ m_buf.AddData((void *)data.c_str(), data.size() + 1); return *this; }
CArchive & operator<<(long data) { return Add(data); }
CArchive & operator<<(int data) { return Add(data); }
CArchive & operator<<(unsigned long data) { return Add(data); }
CArchive & operator<<(unsigned int data) { return Add(data); }
CArchive & operator<<(bool data) { return Add(data); }
};
class CUnArchive
{
private:
CBuffer const * m_pBuf;
string::size_type m_pos;
bool m_isOK;
public:
operator bool ()const{return m_isOK;}
long getPos()const { return m_pos; }
bool StartUnArchive(char const * name, CBuffer const * source)
{
m_pBuf=source;
m_pos=0;
m_isOK=true;
if(0!=strcmp(name,m_pBuf->data()))
{
m_isOK=false;
}
m_pos+=strlen(m_pBuf->data())+1;
return operator bool ();
}
//对各种类型的操作
template
CUnArchive & operator>>(T & data)
{
if (!m_isOK)return *this;
data.UnSerialize(*this);
return *this;
}
template
CUnArchive & Get(T & data)
{
if (m_pos >= m_pBuf->size())
{
thelog << "溢出 " << m_pBuf->size() << " " << m_pos << ende;
m_isOK = false;
return *this;
}
memcpy(&data, m_pBuf->data() + m_pos, sizeof(T));
m_pos += sizeof(T);
return *this;
}
//对基本类型的特化,如果用到其它基本类型会导致“UnSerialize不支持”错误,需要在此增加
template
CUnArchive & operator>>(sstring & data)
{
if (m_pos >= m_pBuf->size())
{
thelog << "溢出 " << m_pBuf->size() << " " << m_pos << ende;
m_isOK = false;
return *this;
}
data = m_pBuf->data() + m_pos;
m_pos += strlen(m_pBuf->data() + m_pos) + 1;//必须用原始数据计算长度
return *this;
}
CUnArchive & operator>>(string & data)
{
if (m_pos >= m_pBuf->size())
{
thelog << "溢出 " << m_pBuf->size() << " " << m_pos << ende;
m_isOK = false;
return *this;
}
data = m_pBuf->data() + m_pos;
m_pos += strlen(m_pBuf->data() + m_pos) + 1;//必须用原始数据计算长度
return *this;
}
CUnArchive & operator>>(long & data) { return Get(data); }
CUnArchive & operator>>(int & data) { return Get(data); }
CUnArchive & operator>>(unsigned long & data) { return Get(data); }
CUnArchive & operator>>(unsigned int & data) { return Get(data); }
CUnArchive & operator>>(bool & data) { return Get(data); }
};
class CArchiveTest
{
public:
struct A
{
bool a;
int b;
long c;
string d;
sstring<16> f;
void Serialize(CArchive & ar)const
{
ar << a << b << c << d << f;
}
void UnSerialize(CUnArchive & unar)
{
unar >> a >> b >> c >> d >> f;
}
};
static int CArchiveTest_doTest(int argc,char ** argv)
{
CArchive ar;
A tmp;
tmp.a=true;
tmp.b=1;
tmp.c=2;
tmp.d="3";
tmp.f="4";
ar.StartArchive("a");
ar<>tmp2;
thelog<
代码里面用到的buffer类在这里:代码设计:C++ 一个保证带有结束符的缓冲区类(源码)-CSDN博客
这个类并没有直接写文件,只是把数据写到了buffer里。
这个类仍然需要对序列化和反序列化写两次代码,仅仅是操作运算符不同(>>和<<),仍然不是很理想,我后来在一个XML配置文件功能里用了这个办法:在类里面用成员变量“bool isSerialize;”来表示处理方向,这样代码就大幅简化,我想或许也可以用在这个类里面。
参考代码:
//不要直接使用这类,使用CXmlArchive_Save和CXmlArchive_Load
class CXmlArchive
{
private:
bool m_bSave;
TiXmlNode* m_pParentNode;
TiXmlNode* AddOrGet(char const* name)
{
if (m_bSave)
{
return CmyXML::Add(m_pParentNode->ToElement(), name);
}
else
{
return CmyXML::Get(m_pParentNode->ToElement(), name);
}
}
public:
CXmlArchive(bool bSave, TiXmlNode* pParentNode) :m_bSave(bSave), m_pParentNode(pParentNode) {}
bool isSave()const { return m_bSave; }
//通用,必须实现XmlArchive
template
CXmlArchive& operator()(char const* name, T& data)
{
CXmlArchive new_archive = *this;
new_archive.m_pParentNode = AddOrGet(name);
data.XmlArchive(new_archive);
return *this;
}
//vector的实现
template
CXmlArchive& operator()(char const* name, vector& data);
//String的实现,如果包含中文或以x开头,转码为x开头的16进制
CXmlArchive& operator()(char const* name, CString& data);
CXmlArchive& operator()(char const* name, int& data)
{
if (m_bSave)
{
CmyXML::Add(m_pParentNode->ToElement(), name, data);
}
else
{
CmyXML::Get(m_pParentNode->ToElement(), name, data);
}
return *this;
}
CXmlArchive& operator()(char const* name, double& data)
{
if (m_bSave)
{
CmyXML::Add(m_pParentNode->ToElement(), name, data);
}
else
{
CmyXML::Get(m_pParentNode->ToElement(), name, data);
}
return *this;
}
CXmlArchive& operator()(char const* name, bool& data)
{
if (m_bSave)
{
CmyXML::Add(m_pParentNode->ToElement(), name, data);
}
else
{
CmyXML::Get(m_pParentNode->ToElement(), name, data);
}
return *this;
}
};
class CXmlArchive_Save : public CXmlArchive
{
public:
CXmlArchive_Save(int/*无意义,避免单参数构造函数*/, TiXmlNode* pParentNode) :CXmlArchive(true, pParentNode) {}
};
class CXmlArchive_Load : public CXmlArchive
{
public:
CXmlArchive_Load(int/*无意义,避免单参数构造函数*/, TiXmlNode* pParentNode) :CXmlArchive(false, pParentNode) {}
};
这个代码用起来是相当舒服的:
//数据类里面增加这个即可,不用关心方向
void XmlArchive(CXmlArchive& ar)
{
ar("ID", ID);
ar("Name", Name);
ar("Status", Status);
}
//保存的时候这样写,其中root是已经打开的tinyXML文档
CXmlArchive_Save ar(0, root);
ar("vList", vDevList);
(这里是结束)