代码设计:C++ 一个序列化Archive类

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


        序列化是一个常见的功能,与数据库、配置文件不同,序列化一般是以二进制方式存储的,不考虑肉眼可读性。

        序列化的基本原理很简单,用同样的顺序写入写出即可。主要需要琢磨的是如何用尽可能简洁的方式来实现,客户代码需要添加的部分越少越好。

        这是我写过的一个版本(测试代码也在里面):

//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<typename T> 
		CArchive & operator<<(T const & data)
		{
			data.Serialize(*this);
			return *this;
		}
		template<typename T> 
		CArchive & Add(T const & data)
		{
			m_buf.AddData(&data,sizeof(T));
			return *this;
		}
		//对基本类型的特化,如果用到其它基本类型会导致“Serialize不支持”错误,需要在此增加
		template <long BUFSIZE >
		CArchive & operator<<(sstring<BUFSIZE> 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<typename T>
		CUnArchive & operator>>(T & data)
		{
			if (!m_isOK)return *this;
			data.UnSerialize(*this);
			return *this;
		}
		template<typename T> 
		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 <long BUFSIZE >
		CUnArchive & operator>>(sstring<BUFSIZE> & 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<<tmp;

			CUnArchive unar;
			if(!unar.StartUnArchive("a",&ar.m_buf))return __LINE__;
			A tmp2;
			unar>>tmp2;
			thelog<<tmp2.a<<endi;
			thelog<<tmp2.b<<endi;
			thelog<<tmp2.c<<endi;
			thelog<<tmp2.d<<endi;
			thelog<<tmp2.f.c_str()<<endi;
			return 0;
		}
	};
}

#endif

        代码里面用到的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<typename T>
	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<typename T>
	CXmlArchive& operator()(char const* name, vector<T>& 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);


(这里是结束)

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
C++ 中可以通过将的成员变量进行序列化,将其转换为二进制格式,并将其保存到文件中,以实现序列化操作。反序列化则是将二进制格式的数据读取出来,并将其转换为的成员变量,再进行实例化的操作。 以下是一个简单的示例代码,演示了如何将一个序列化再反序列化: ```c++ #include <iostream> #include <fstream> #include <string> using namespace std; class Person { public: string name; int age; void serialize(const string& filename) { ofstream ofs(filename, ios::binary); ofs.write(name.c_str(), name.size() + 1); ofs.write(reinterpret_cast<const char*>(&age), sizeof(age)); } void deserialize(const string& filename) { ifstream ifs(filename, ios::binary); char buf[1024]; ifs.read(buf, sizeof(buf)); name = buf; ifs.read(reinterpret_cast<char*>(&age), sizeof(age)); } }; int main() { Person p1; p1.name = "Tom"; p1.age = 20; p1.serialize("person.bin"); Person p2; p2.deserialize("person.bin"); cout << p2.name << ", " << p2.age << endl; // 输出:Tom, 20 return 0; } ``` 在上面的示例代码中,我们定义了一个名为 Person 的,其中包含了一个字符串型的 name 成员变量和一个整型的 age 成员变量。我们在 Person 中定义了两个函数,一个是 serialize() 函数,用于将 Person 序列化,另一个是 deserialize() 函数,用于将 Person 序列化。在 serialize() 函数中,我们使用 ofstream 型的对象 ofs 将 name 和 age 成员变量以二进制格式写入到文件中;在 deserialize() 函数中,我们使用 ifstream 型的对象 ifs 从文件中读取出二进制数据,并将其转换为 Person 的成员变量。 在 main() 函数中,我们首先创建了一个 Person 的实例 p1,并将其 name 和 age 成员变量设置为 "Tom" 和 20。随后,我们调用了 p1 的 serialize() 函数,将其序列化,并保存到了 person.bin 文件中。接着,我们创建了另一个 Person 的实例 p2,并调用了其 deserialize() 函数,从 person.bin 文件中读取出二进制数据,并将其转换为 p2 的成员变量。最后,我们输出了 p2 的 name 和 age 成员变量,验证了反序列化操作是否成功。 需要注意的是,在序列化和反序列化的过程中,我们使用了文件流对象 ofstream 和 ifstream 来进行读写操作,并使用了二进制格式来保存数据。同时,我们还需要使用 reinterpret_cast 来将指针型进行转换,以便正确地将数据写入到文件中或读取出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值