Boost序列化全解析

news2024/12/27 0:38:22

程序开发中,序列化是经常需要用到的。像一些相对高级语言,比如JAVA, C#都已经很好的支持了序列化,那么C++呢?当然一个比较好的选择就是用Boost,这个号称C++准标准库的东西。

什么时候需要序列化呢?举个例子,我们定义了一个class,比如:

class CCar
{
public:
	void SetName(std::string& strName){m_strName = strName;}
	std::string GetName() const{return m_strName;}
private:
	std::string m_strName;
};

然后我们想把这个类的一个对象保存到文件中或者通过网络发出去,怎么办呢?答案就是:把这个对象序列化,然后我们可以得到一个二进制字节流,或者XML格式表示等等。

这样我们就可以保存这个对象到文件中或者通过网络发出去了。把序列化的数据进行反序列化,就可以得到一个CCar对象了。

Boost已经很好的支持了序列化这个东西,很好很强大。

Boost网站上有介绍: Serialization

对于序列化,Boost是这么定义的:

Here, we use the term "serialization" to mean the reversible deconstruction of an arbitrary set of C++ data structures to a sequence of bytes. Such a system can be used to reconstitute an equivalent structure in another program context. Depending on the context, this might used implement object persistence, remote parameter passing or other facility. In this system we use the term"archive" to refer to a specific rendering of this stream of bytes. This could be a file of binary data, text data, XML, or some other created by the user of this library.

这段英文很简单,我相信大多数程序员都能看的懂。

基本上Boost序列化可以分为两种模式:侵入式(intrusive)和非侵入式(non-intrusive)

侵入式(intrusive)

先来看看侵入式。我们先来定义一个类,这个类支持序列化:

class CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & _tag;
		ar & _text;
	}
 
	
public:
	CMyData():_tag(0), _text(""){}
 
	CMyData(int tag, std::string text):_tag(tag), _text(text){}
 
	int GetTag() const {return _tag;}
	std::string GetText() const {return _text;}
 
private:
	int _tag;
	std::string _text;
};

其中,我们可以看到这些代码:

friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & _tag;
		ar & _text;
	}

这些代码就是用来实现序列化的,这些代码存在于类CMyData中,也就是为什么称这种模式是“侵入式”的原因了。

看看怎么把这个对象序列化。这里,我把这个对象以二进制的方式保存到了一个ostringstream中了,当然也可以保存为其他形式,比如XML。也可以保存到文件中。代码都是类似的。

void TestArchive1()
{
	CMyData d1(2012, "China, good luck");
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData tag: " << d2.GetTag() << ", text: " << d2.GetText() << "\n";
}

先生成一个CMyData的对象,然后序列化保存到一个ostringstream中,接着再把这个序列化的数据反序列化,得到原来的对象,打印出来,我们会发现反序列化的对象的数据成员跟序列化前的对象一模一样。哈哈,成功了,简单吧。至于Boost怎么实现这个过程的,看Boost源代码吧,Boost的网站上也有一些介绍。Boost确实设计的很巧妙,不得不佩服那帮家伙。

那么可以序列化CMyData的子类吗,答案是肯定的。其实很简单就是在子类的序列化函数里面先序列化基类的。看看代码就明白了:

class CMyData_Child: public CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		// serialize base class information
		ar & boost::serialization::base_object<CMyData>(*this);
		ar & _number;
	}
 
 
public:
	CMyData_Child():_number(0.0){}
 
	CMyData_Child(int tag, std::string text, float number):CMyData(tag, text), _number(number){}
 
	float GetNumber() const{return _number;}
 
private:
	float _number;
};
 
void TestArchive3()
{
	CMyData_Child d1(2012, "China, good luck", 1.2);
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData_Child d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData_Child tag: " << d2.GetTag() << ", text: " << d2.GetText() << ", number: "<<d2.GetNumber() << "\n";
}

相关视频推荐

boost.asio是什么?解决了网络编程中哪些痛点?

手把手带你看 mmorpg 开源框架的网络模块封装

源码阅读:STL 红黑树、散列表的实现

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

非侵入式(non-intrusive)

侵入式的缺点就是需要在class里面加一些代码,那么有时候可能这个class已经存在了,或者我们并不想往里面加入这么些代码,那么怎么办呢?ok,轮到非侵入式出场了。

比方说我们有这么个类:

class CMyData2
{
public:
	CMyData2():_tag(0), _text(""){}
 
	CMyData2(int tag, std::string text):_tag(tag), _text(text){}
 
	int _tag;
	std::string _text;
};

那么我们可以这么序列化:

namespace boost {
	namespace serialization {
 
		template<class Archive>
		void serialize(Archive & ar, CMyData2 & d, const unsigned int version)
		{
			ar & d._tag;
			ar & d._text;
		}
 
	} // namespace serialization
} // namespace boost

然后调用还是跟侵入式一模一样,看:

void TestArchive2()
{
	CMyData2 d1(2012, "China, good luck");
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData2 d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData2 tag: " << d2._tag << ", text: " << d2._text << "\n";
}

成功。跟侵入式相比,非侵入式省去了在具体类里面加入序列化代码。但是我们看看非侵入式模式里面的类的定义,我们会发现我们把数据成员搞成public的了。这是为什么呢?看看这个就明白了:

template<class Archive>
		void serialize(Archive & ar, CMyData2 & d, const unsigned int version)
		{
			ar & d._tag;
			ar & d._text;
		}

原来序列化函数需要访问数据成员。这就是非侵入式的一个缺点了:需要把数据成员暴露出来。通过直接访问数据成员也好,通过函数访问也好,总之需要这个类把数据成员暴露出来,这样序列化函数才能访问。世界上没有十全十美的东西,有时我们得到一个东西,往往会失去另外一个东西,不是吗?

侵入式和非侵入式各有各的用处,看具体情况来决定用哪个了。

非侵入式可以支持子类序列化吗?可以。跟侵入式一样,其实也就是先序列化一下基类,然后再序列化子类的数据成员。看代码:

class CMyData2_Child: public CMyData2
{
public:
	CMyData2_Child():_number(0.0){}
 
	CMyData2_Child(int tag, std::string text, float number):CMyData2(tag, text), _number(number){}
 
	float _number;
};
 
namespace boost {
	namespace serialization {
 
		template<class Archive>
		void serialize(Archive & ar, CMyData2_Child & d, const unsigned int version)
		{
			// serialize base class information
			ar & boost::serialization::base_object<CMyData2>(d);
			ar & d._number;
		}
 
	} // namespace serialization
} // namespace boost
 
void TestArchive4()
{
	CMyData2_Child d1(2012, "test non-intrusive child class", 5.6);
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData2_Child d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData2_Child tag: " << d2._tag << ", text: " << d2._text << ", number: "<<d2._number<<"\n";
}

好了,以上就是序列化的简单用法。接下里我们来重点关注一下数据成员的序列化,假如我们的类里面有指针,那么还能序列化吗?比如下面的代码,会发生什么事?

序列化指针数据成员

class CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & _tag;
		ar & _text;
	}
 
 
public:
	CMyData():_tag(0), _text(""){}
 
	CMyData(int tag, std::string text):_tag(tag), _text(text){}
 
	int GetTag() const {return _tag;}
	std::string GetText() const {return _text;}
 
private:
	int _tag;
	std::string _text;
};
class CMyData_Child: public CMyData
{
private:
	friend class boost::serialization::access;
 
	template
  
  
   
   
	void serialize(Archive& ar, const unsigned int version)
	{
		// serialize base class information
		ar & boost::serialization::base_object
   
   
    
    (*this);
		ar & _number;
	}
 
 
public:
	CMyData_Child():_number(0.0){}
 
	CMyData_Child(int tag, std::string text, float number):CMyData(tag, text), _number(number){}
 
	float GetNumber() const{return _number;}
 
private:
	float _number;
};
class CMyData_Container
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		for(int i = 0; i < 3; i++)
		{
			ar & pointers[i];
		}
	}
public:
	CMyData* pointers[3];
};
 
void TestPointerArchive()
{
	std::string content;
	{
		CMyData d1(1, "a");
		CMyData_Child d2(2, "b", 1.5);
 
		CMyData_Container containter;
		containter.pointers[0] = &d1;
		containter.pointers[1] = &d2;
		containter.pointers[2] = &d1;
 
		std::ostringstream os;
		boost::archive::binary_oarchive oa(os);
		oa << containter;
 
		content = os.str();
	}
 
	//反序列化
	{
		CMyData_Container container;
		std::istringstream is(content);
		boost::archive::binary_iarchive ia(is);
		ia >> container;
 
		for (int i = 0; i < 3; i++)
		{
			CMyData* d = container.pointers[i];
			std::cout << "pointer" << i + 1 <<": " << d->GetText() << "\n";
 
			if (i == 1)
			{
				CMyData_Child* child = reinterpret_cast<CMyData_Child*>(d);
				std::cout << "pointer" << i + 1 <<", number: " << child->GetNumber() << "\n";
			}
		}
	}
}

注意,我们在CMyData_Container对象里面放进去了3个指针,其中第二个指针是CMyData的子类。

然后进行序列化,再反序列化,我们会发现,第一个,第三个指针输出了正确的信息,然而第二个指针有点问题,本身我们存进去的时候是个CMyData_Child 对象,通过测试我们可以发现,CMyData_Child的基类部分,我们可以正确的输出,但是CMyData_Child的成员_number,却得不到正确信息。这是个问题。

也就是说,序列化指针是可以的,但是需要注意多态的问题。假如我们不需要考虑多态,那么以上的代码就可以正常工作了。但是如果要考虑多态的问题,那么就得特殊处理了。下面再来介绍序列化多态指针。

序列化多态指针数据成员

上一个章节里面演示了如果序列化指针成员,但是有个问题,就是当基类指针指向一个派生类对象的时候,然后序列化这个指针,那么派生类的信息就被丢掉了。这个很不好。那么怎么来解决这个问题呢?很幸运,Boost的开发人员已经考虑到了这个问题。再一次感受到Boost的强大。

有两种方法可以解决这个问题:

1. registration

2. export

具体可以参考: Serialization - Serialization of Classes

这里我们介绍第二种方式,这种方式比较简单,也用的比较好。就是通过一个宏把派生类给命名一下。

这个关键的宏是:BOOST_CLASS_EXPORT_GUID

相关解释:

The macro BOOST_CLASS_EXPORT_GUID associates a string literal with a class. In the above example we've used a string rendering of the class name. If a object of such an "exported" class is serialized through a pointer and is otherwise unregistered, the "export" string is included in the archive. When the archive is later read, the string literal is used to find the class which should be created by the serialization library. This permits each class to be in a separate header file along with its string identifier. There is no need to maintain a separate "pre-registration" of derived classes that might be serialized. This method of registration is referred to as "key export".

如何使用这个神奇的宏BOOST_CLASS_EXPORT_GUID来实现序列化指向派生类的指针呢?先给出代码:

class CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & _tag;
		ar & _text;
	}
 
	
public:
	CMyData():_tag(0), _text(""){}
 
	CMyData(int tag, std::string text):_tag(tag), _text(text){}
	virtual ~CMyData(){}
 
	int GetTag() const {return _tag;}
	std::string GetText() const {return _text;}
 
private:
	int _tag;
	std::string _text;
};
 
class CMyData_Child: public CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		// serialize base class information
		ar & boost::serialization::base_object<CMyData>(*this);
		ar & _number;
	}
 
 
public:
	CMyData_Child():_number(0.0){}
 
	CMyData_Child(int tag, std::string text, float number):CMyData(tag, text), _number(number){}
 
	float GetNumber() const{return _number;}
 
private:
	float _number;
};
 
BOOST_CLASS_EXPORT_GUID(CMyData_Child, "CMyData_Child")
 
class CMyData_Container
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		for(int i = 0; i < 3; i++)
		{
			ar & pointers[i];
		}
	}
public:
	CMyData* pointers[3];
};
 
 
void TestPointerArchive()
{
	std::string content;
	{
		CMyData d1(1, "a");
		CMyData_Child d2(2, "b", 1.5);
 
		CMyData_Container containter;
		containter.pointers[0] = &d1;
		containter.pointers[1] = &d2;
		containter.pointers[2] = &d1;
 
		std::ostringstream os;
		boost::archive::binary_oarchive oa(os);
		oa << containter;
 
		content = os.str();
	}
 
	//·´ÐòÁл¯
	{
		CMyData_Container container;
		std::istringstream is(content);
		boost::archive::binary_iarchive ia(is);
		ia >> container;
 
		for (int i = 0; i < 3; i++)
		{
			CMyData* d = container.pointers[i];
			std::cout << "pointer" << i + 1 <<": " << d->GetText() << "\n";
 
			if (i == 1)
			{
				CMyData_Child* child = reinterpret_cast<CMyData_Child*>(d);
				std::cout << "pointer" << i + 1 <<", number: " << child->GetNumber() << "\n";
			}
		}
	}
}

这次我们可以正确的读取到第二个指针指向的对象了,可以看到_number的争取值了。

把代码和上个版本想比较,我们会发现2个不同:

1. CMyData类里面多了个虚的析构函数;

2. 调用BOOST_CLASS_EXPORT_GUID给派生类CMyData_Child绑定一个字符串。

第二点很容易理解,就是给某个派生类命名一下,这样就可以当作一个key来找到相应的类。那么第一点为什么要增加一个虚析构函数呢?是我无意中添加的吗?当然不是,其实这个是序列化指向派生类的指针的其中一个关键。先看Boost网站上面的一段描述:

It turns out that the kind of object serialized depends upon whether the base class (base in this case) is polymophic or not. Ifbase is not polymorphic, that is if it has no virtual functions, then an object of the typebasewill be serialized. Information in any derived classes will be lost. If this is what is desired (it usually isn't) then no other effort is required.

If the base class is polymorphic, an object of the most derived type (derived_oneorderived_twoin this case) will be serialized. The question of which type of object is to be serialized is (almost) automatically handled by the library.

ok,通过这段描述,我们发现Boost序列化库会判断基类是不是多态的。判断的依据就是这个基类里面有没有虚函数。我们知道,当一个类里面有虚函数的时候,C++编译器会自动给这个类增加一个成员:_vfptr,就是虚函数表指针。我没有花太多时间去看Boost有关这部分的源代码,但是我猜测Boost是根据这个_vfptr来判断是需要序列化基类,还是派生类的。我们增加一个虚析构函数的目的也就是让CMyData产生一个_vfptr。我们可以试一下把上面的代码里面的析构函数改成非虚的,那么派生类序列化就会失败,跟上一个章节得到相同的结果。至于Boost怎么知道该序列化哪个派生类,相信这个是BOOST_CLASS_EXPORT_GUID的功劳,至于怎么实现,还是需要看源代码,但是我自己没有仔细研究过,有兴趣的朋友可以学习Boost的源代码。Boost的设计很巧妙,我们可以学到不少东西。当然这个得有时间细细学习。好了,序列化指向派生类指针就2个要点:

1. 让Boost知道基类是多态的,其实就是确保基类里面有个虚函数;

2. 通过BOOST_CLASS_EXPORT_GUID给派生类绑定一个字符串,当作一个key。

至于第一种序列化指向派生类的基类指针:registration,可以参考http://www.boost.org/doc/libs/1_51_0/libs/serialization/doc/serialization.html#derivedpointers,上面讲的非常清楚。我本人很少使用这种方式,这里也就略过不讲了。

序列化数组

一个小细节,上面讲到的序列化指针章节里面,我们看到代码:

class CMyData_Container
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		for(int i = 0; i < 3; i++)
		{
			ar & pointers[i];
		}
	}
public:
	CMyData* pointers[3];
};

其中的序列化函数里面有个for循环,难道每次序列化一个数组都需要弄一个for语句吗,这个是不是可以改进呢?答案是肯定的。Boost自己会检测数组。也就是说我们可以把代码改成下面的形式:

class CMyData_Container
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & pointers;
	}
public:
	CMyData* pointers[3];
};

代码短了很多,方便吧。

支持STL容器

上面我们使用了一个普通数组来保存指针,我相信在平常写程序过程中,大家都会使用STL容器,比如list,map,array等等。至少我自己是经常使用的。那么Boost序列化库可以序列化STL容器吗?很幸运,Boost序列化库已经支持了STL容器。原话是:

The above example uses an array of members. More likely such an application would use an STL collection for such a purpose. The serialization library contains code for serialization of all STL classes. Hence, the reformulation below will also work as one would expect.

我们一开始就是用std::string作为CMyData的一个成员,我们不需要做任何工作就可以直接序列化std::string,这是因为Boost序列化库已经支持std::string了。从上面的英文描述里面可以看到Boost serialization库可以支持所有STL类,神奇吧。至少我本人经常使用std::list, vector, map, string等,都可以正常工作。下面的代码使用std::vector代替了普通数组,可以正常工作。

class CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & _tag;
		ar & _text;
	}
 
	
public:
	CMyData():_tag(0), _text(""){}
 
	CMyData(int tag, std::string text):_tag(tag), _text(text){}
	virtual ~CMyData(){}
 
	int GetTag() const {return _tag;}
	std::string GetText() const {return _text;}
 
private:
	int _tag;
	std::string _text;
};
 
class CMyData_Child: public CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		// serialize base class information
		ar & boost::serialization::base_object<CMyData>(*this);
		ar & _number;
	}
 
 
public:
	CMyData_Child():_number(0.0){}
 
	CMyData_Child(int tag, std::string text, float number):CMyData(tag, text), _number(number){}
 
	float GetNumber() const{return _number;}
 
private:
	float _number;
};
 
BOOST_CLASS_EXPORT_GUID(CMyData_Child, "CMyData_Child")
 
//ʹÓÃSTLÈÝÆ÷
class CMyData_ContainerSTL
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & vPointers;
	}
public:
	std::vector<CMyData*> vPointers;
};
 
 
 
void TestPointerArchiveWithSTLCollections()
{
	std::string content;
	{
		CMyData d1(1, "parent obj");
		CMyData_Child d2(2, "child obj", 2.5);
 
		CMyData_ContainerSTL containter;
		containter.vPointers.push_back(&d1);
		containter.vPointers.push_back(&d2);
		containter.vPointers.push_back(&d1);
		
 
		std::ostringstream os;
		boost::archive::binary_oarchive oa(os);
		oa << containter;
 
		content = os.str();
	}
 
	//·´ÐòÁл¯
	{
		CMyData_ContainerSTL container;
		std::istringstream is(content);
		boost::archive::binary_iarchive ia(is);
		ia >> container;
 
		std::cout<<"Test STL collections:\n";
		BOOST_FOREACH(CMyData* p, container.vPointers)
		{
			std::cout << "object text: " << p->GetText() << "\n";
 
			CMyData_Child* child = dynamic_cast<CMyData_Child*>(p);
			if (child)
			{
				std::cout << "child object number: " << child->GetNumber() << "\n";
			}
		}
	}
}

一不小心就用到了BOOST_FOREACH,看来这个确实很好用啊,呵呵。省去了写很长的iterator来遍历整个vector。

class版本

再来考虑一个问题,比方说现在我们程序升级了,然后把某个类给升级了一下,加了一个成员,那么之前保存的序列化的数据还能匹配到新的类吗?看一下序列化函数,我们会发现这个序列化函数有个参数,叫做version

template<class Archive>

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

通过这个参数,我们就可以解决class版本的问题。看这段描述

In general, the serialization library stores a version number in the archive for each class serialized. By default this version number is 0. When the archive is loaded, the version number under which it was saved is read.

也就是说如果我们不刻意指定version,那么Boost序列化库就会默认设置为0并且保存到序列化结果中。

如果我们要标记不同的class版本,可以使用宏BOOST_CLASS_VERSION,比如

BOOST_CLASS_VERSION(CMyData, 1)

具体这里就不举例了。参考Boost说明。

save和load分开

一直到现在我们都是用了一个序列化函数

template<class Archive>

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

其实,序列化包括序列化和发序列化两部分,或者称之为save和load,甚至mashalling,unmarshalling。反正就这个意思。

还有一个奇怪的地方,就是通常我们输入输出是用<<和>>的,那么在函数serialize里面我们用了&。其实这个是Boost对&做了一个封装。假如现在是做序列化,那么&就等同于<<,假如是反序列化,那么&就等同于>>。然后序列化和反序列化统统用一个函数serialize来实现。这也体现了Boost的巧妙设计。

那么如果有特殊需求,我们需要把序列化和反序列化分开,应该怎么实现呢?

就好比上面的class版本问题,save和load可能就是不一样的,因为load需要考虑兼容旧的版本。这里就偷懒使用Boost文档上的例子了。我们可以看到save和load是分开的。

#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/split_member.hpp>
 
class bus_route
{
    friend class boost::serialization::access;
    std::list<bus_stop *> stops;
    std::string driver_name;
    template<class Archive>
    void save(Archive & ar, const unsigned int version) const
    {
        // note, version is always the latest when saving
        ar  & driver_name;
        ar  & stops;
    }
    template<class Archive>
    void load(Archive & ar, const unsigned int version)
    {
        if(version > 0)
            ar & driver_name;
        ar  & stops;
    }
    BOOST_SERIALIZATION_SPLIT_MEMBER()
public:
    bus_route(){}
};
 

注意需要使用宏BOOST_SERIALIZATION_SPLIT_MEMBER()来告诉Boost序列化库使用save和load代替serialize函数。

到这里,我们几乎把Boost序列化库所有的内容都介绍完毕了。这个库是相当的nice,基本可以cover所有的case。而且就开源库来讲,Boost的说明文档真的算是很好的了。基本上都有详细的说明,就序列化库而言,直接看这个页面就基本ok了,Serialization - Tutorial 相当的详细。尽管读英文比较累,但是可以获得原汁原味的第一手权威资料,花这些功夫还是值得的。

我的例子里面使用了二进制流来保存序列化后的数据,其实还有其他的archive格式,比如text,XML等等。甚至我们可以自己来实现序列化格式。Boost已经定义了一个统一的接口,我们要实现自己的格式,只需要继承相应的Boost::archive里面的类就可以了。

好了,写完了,希望对大家有点帮助。如有错误,欢迎指出。

附,完整测试代码,使用这段代码前需要确保VISUAL STUDIO已经设置了Boost的路径。

// Serialization.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
 
#include "boost/serialization/serialization.hpp"
#include "boost/archive/binary_oarchive.hpp"
#include "boost/archive/binary_iarchive.hpp"
#include <boost/serialization/export.hpp>
#include "boost/foreach.hpp"
#include "boost/any.hpp"
#include <boost/serialization/vector.hpp>
 
 
 
#include <string>
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <vector>
 
 
//测试序列化
class CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & _tag;
		ar & _text;
	}
 
	
public:
	CMyData():_tag(0), _text(""){}
 
	CMyData(int tag, std::string text):_tag(tag), _text(text){}
	virtual ~CMyData(){}
 
	int GetTag() const {return _tag;}
	std::string GetText() const {return _text;}
 
private:
	int _tag;
	std::string _text;
};
 
 
void TestArchive1()
{
	std::string content;
 
	{
		CMyData d1(2012, "China, good luck");
		std::ostringstream os;
		boost::archive::binary_oarchive oa(os);
		oa << d1;//序列化到一个ostringstream里面
 
		content = os.str();//content保存了序列化后的数据。
	}
	
 
	{
		CMyData d2;
		std::istringstream is(content);
		boost::archive::binary_iarchive ia(is);
		ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
		std::cout << "CMyData tag: " << d2.GetTag() << ", text: " << d2.GetText() << "\n";
	}
	
}
 
 
class CMyData2
{
public:
	CMyData2():_tag(0), _text(""){}
 
	CMyData2(int tag, std::string text):_tag(tag), _text(text){}
 
	int _tag;
	std::string _text;
};
 
namespace boost {
	namespace serialization {
 
		template<class Archive>
		void serialize(Archive & ar, CMyData2 & d, const unsigned int version)
		{
			ar & d._tag;
			ar & d._text;
		}
 
	} // namespace serialization
} // namespace boost
 
void TestArchive2()
{
	CMyData2 d1(2012, "China, good luck");
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData2 d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData2 tag: " << d2._tag << ", text: " << d2._text << "\n";
}
 
 
//序列化子类,侵入式
class CMyData_Child: public CMyData
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		// serialize base class information
		ar & boost::serialization::base_object<CMyData>(*this);
		ar & _number;
	}
 
 
public:
	CMyData_Child():_number(0.0){}
 
	CMyData_Child(int tag, std::string text, float number):CMyData(tag, text), _number(number){}
 
	float GetNumber() const{return _number;}
 
private:
	float _number;
};
 
BOOST_CLASS_EXPORT_GUID(CMyData_Child, "CMyData_Child")
 
 
void TestArchive3()
{
	CMyData_Child d1(2012, "China, good luck", 1.2);
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData_Child d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData_Child tag: " << d2.GetTag() << ", text: " << d2.GetText() << ", number: "<<d2.GetNumber() << "\n";
}
 
//序列化子类,非侵入式
class CMyData2_Child: public CMyData2
{
public:
	CMyData2_Child():_number(0.0){}
 
	CMyData2_Child(int tag, std::string text, float number):CMyData2(tag, text), _number(number){}
 
	float _number;
};
 
namespace boost {
	namespace serialization {
 
		template<class Archive>
		void serialize(Archive & ar, CMyData2_Child & d, const unsigned int version)
		{
			// serialize base class information
			ar & boost::serialization::base_object<CMyData2>(d);
			ar & d._number;
		}
 
	} // namespace serialization
} // namespace boost
 
void TestArchive4()
{
	CMyData2_Child d1(2012, "test non-intrusive child class", 5.6);
	std::ostringstream os;
	boost::archive::binary_oarchive oa(os);
	oa << d1;//序列化到一个ostringstream里面
 
	std::string content = os.str();//content保存了序列化后的数据。
 
	CMyData2_Child d2;
	std::istringstream is(content);
	boost::archive::binary_iarchive ia(is);
	ia >> d2;//从一个保存序列化数据的string里面反序列化,从而得到原来的对象。
 
	std::cout << "CMyData2_Child tag: " << d2._tag << ", text: " << d2._text << ", number: "<<d2._number<<"\n";
}
 
 
//指针数据成员
 
class CMyData_Container
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & pointers;
	}
public:
	CMyData* pointers[3];
};
 
 
 
void TestPointerArchive()
{
	std::string content;
	{
		CMyData d1(1, "a");
		CMyData_Child d2(2, "b", 1.5);
 
		CMyData_Container containter;
		containter.pointers[0] = &d1;
		containter.pointers[1] = &d2;
		containter.pointers[2] = &d1;
 
		std::ostringstream os;
		boost::archive::binary_oarchive oa(os);
		oa << containter;
 
		content = os.str();
	}
 
	//反序列化
	{
		CMyData_Container container;
		std::istringstream is(content);
		boost::archive::binary_iarchive ia(is);
		ia >> container;
 
		for (int i = 0; i < 3; i++)
		{
			CMyData* d = container.pointers[i];
			std::cout << "pointer" << i + 1 <<": " << d->GetText() << "\n";
 
			if (i == 1)
			{
				CMyData_Child* child = reinterpret_cast<CMyData_Child*>(d);
				std::cout << "pointer" << i + 1 <<", number: " << child->GetNumber() << "\n";
			}
		}
	}
}
 
//使用STL容器
class CMyData_ContainerSTL
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive& ar, const unsigned int version)
	{
		ar & vPointers;
	}
public:
	std::vector<CMyData*> vPointers;
};
 
 
 
void TestPointerArchiveWithSTLCollections()
{
	std::string content;
	{
		CMyData d1(1, "parent obj");
		CMyData_Child d2(2, "child obj", 2.5);
 
		CMyData_ContainerSTL containter;
		containter.vPointers.push_back(&d1);
		containter.vPointers.push_back(&d2);
		containter.vPointers.push_back(&d1);
		
 
		std::ostringstream os;
		boost::archive::binary_oarchive oa(os);
		oa << containter;
 
		content = os.str();
	}
 
	//反序列化
	{
		CMyData_ContainerSTL container;
		std::istringstream is(content);
		boost::archive::binary_iarchive ia(is);
		ia >> container;
 
		std::cout<<"Test STL collections:\n";
		BOOST_FOREACH(CMyData* p, container.vPointers)
		{
			std::cout << "object text: " << p->GetText() << "\n";
 
			CMyData_Child* child = dynamic_cast<CMyData_Child*>(p);
			if (child)
			{
				std::cout << "child object number: " << child->GetNumber() << "\n";
			}
		}
	}
}
 
int _tmain(int argc, _TCHAR* argv[])
{
	TestArchive1();
 
	TestArchive2();
 
	TestArchive3();
 
	TestArchive4();
 
	TestPointerArchive();
 
	TestPointerArchiveWithSTLCollections();
 
	return 0;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/683919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

可视化的工时管理:让项目进度真实可见

在现代项目管理中&#xff0c;工时表软件作为一种强大而有效的工具&#xff0c;能够帮助团队更好地管理项目进度。无论是大小型项目&#xff0c;正确使用工时表软件都可以提高团队的效率和项目的可追踪性。本文将介绍一些关键步骤&#xff0c;以帮助企业利用工时表软件来管理项…

【计算机图形学】期末总结大全,建议收藏

文章目录 一、图形学及其研究内容二、图形的输入设备和显示设备三、图形的显示设备四、显示子系统五、图形软件标准五、图形软件包六、习题七、直线段扫描转换算法八、直线段扫描转换算法练习题九、扫描线填充算法十、实区域填充算法十一、反走样技术十二、图形裁剪基础概念十三…

开源社区必会知识点— —git提交pr

开源社区必会 1 fork仓库并提交之后给开源社区提交pr 1.1 fork开源仓库 ①登录github&#xff0c;找到开源仓库A&#xff0c;然后点击fork 这样&#xff0c;就会在你自己github账号下创建一个同名的仓库B&#xff08;仓库名可修改&#xff09; ②然后本地修改&#xff0c;提…

[RocketMQ] Consumer消费者启动主要流程源码 (六)

客户端常用的消费者类是DefaultMQPushConsumer, DefaultMQPushConsumer的构造器以及start方法的源码。 1.创建DefaultMQPushConsumer实例 最终都是调用下面四个参数的构造函数: /*** 创建DefaultMQPushConsumer实例** param namespace namespace地址* par…

调用聚合数据API实现手机号码归属地查询

调用聚合数据API实现手机号码归属地查询 1&#xff0e;作者介绍2&#xff0e;相关介绍2.1 什么是聚合数据&#xff1f;2.2 API介绍2.3 手机号码归属地 3&#xff0e;实验过程3.1如何调用聚合数据API3.2代码实现3.3实验结果3.4问题分析 1&#xff0e;作者介绍 吝红凯&#xff0…

Python+requests+unittest+excel搭建接口自动化测试框架

一、框架结构&#xff1a; 工程目录 代码&#xff1a;基于python2编写 二、Case文件设计 三、基础包 base 3.1 封装get/post请求&#xff08;runmethon.py&#xff09; import requests import json class RunMethod:def post_main(self,url,data,headerNone):res Noneif h…

【C++】一些关于visual stdio,vscode,Mingw的思考 |bug

文章目录 今天在做YOLOV8的C部署时遇到的一些问题&#xff1a; 在进行一系列的操作之后会生成解决方案文件sln: 当然按道理到这一步之后&#xff0c;应该使用make命令进行下一步操作&#xff08;但是我确实不会make命令&#xff0c;所以准备进sln来生成解决方案&#xff09;&…

缓存和数据库一致性问题,看这篇就够了

阅读本文大约需要 10 分钟。 如何保证缓存和数据库一致性&#xff0c;这是一个老生常谈的话题了。 但很多人对这个问题&#xff0c;依旧有很多疑惑&#xff1a; 到底是更新缓存还是删缓存&#xff1f; 到底选择先更新数据库&#xff0c;再删除缓存&#xff0c;还是先删除缓存…

消息队列详解

文章目录 1、什么是消息队列2、使用场景3、消息队列与传统设计的区别1、传统设计2、并行处理调优3、消息队列 4、三大优点1、异步2、削峰3、解耦 5、缺点1、增加了系统复杂性。2、事务问题。3、可用性 6、MQ常见问题1、消息堆积问题怎么解决2、重复消费问题怎么解决3、如果避免…

消息队列Message Queue 0基础学习

一、定义 消息队列&#xff1a;一般我们会简称它为MQ(Message Queue)。Message Query&#xff08;MQ&#xff09;&#xff0c;消息队列中间件&#xff0c;很多初学者认为&#xff0c;MQ通过消息的发送和接受来实现程序的异步和解耦&#xff0c;mq主要用于异步操作&#xff0c;…

高效精细的企业发票管理:探索优质方案助力您提升财务效率

随着企业逐渐规范化&#xff0c;财务工作流程暴露了不足&#xff0c;在企业员工报销管理工作中常遇到以下问题&#xff1a; 1. 报销发票种类繁多&#xff0c;管理麻烦费时&#xff1b; 2. 手动合计报销金额费时费力、并且易出错&#xff1b; 3. 报销流程繁杂&#xff0c;填写…

【软考网络管理员】2023年软考网管初级常见知识考点(5)-以太网技术

涉及知识点 CSMA/CD技术&#xff0c;以太网帧结构&#xff0c;冲突域和广播域&#xff0c;高速以太网&#xff0c;交换式以太网&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 文章目录 涉及知识点前言一、CSMA/CD…

【CV】常见的损失函数及应用举例:交叉熵、对比、余弦、Dice、Focal Loss

目录 前言均方误差&#xff08;MSE&#xff09;交叉熵损失&#xff08;Cross-Entropy Loss&#xff09;对比损失&#xff08;Contrastive Loss&#xff09;余弦相似度损失&#xff08;Cosine Similarity Loss&#xff09;交叉熵损失加权的Dice损失&#xff08;Dice Loss&#x…

【接口自动化测试】如何进行流程封装和基于加密接口的测试用例设计?

接口测试仅仅掌握 Requests 或者其他一些功能强大的库的用法&#xff0c;是远远不够的&#xff0c;还需要具备能根据公司的业务流程以及需求去定制化一个接口自动化测试框架的能力。所以&#xff0c;接下来&#xff0c;我们主要介绍下接口测试用例分析以及通用的流程封装是如何…

何为SaaS?国内做的好的SaaS平台有哪些?

国内做得好的saas平台有哪些啊&#xff1f; 什么是SaaS平台&#xff1f; SaaS是Software as a Service的缩写&#xff0c;意为软件即服务。 它是指将软件应用程序部署在云计算服务器上&#xff0c;通过网络提供给用户的一种模式。这个模式下&#xff0c;用户无需花费大量的资…

LNMP架构搭建

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、LNMP概述1.LNMP的特点2.LNMP工作原理 二、安装Nginx 服务1.安装需要的依赖包2.创建运行用户3.编译安装4.优化路径5.添加 Nginx 系统服务 三、安装mysql服务1.安…

机房如何选购STS静态转换开关,采购配置并上架投入使用

环境: 1.机房交换机设备 2.STS静态转换开关 3.16安4平方电源插头 4.6平方输入连接线 5.大功率接线器(3进3出) 6.PDU(C14插头) 问题描述: 机房如何选购STS静态转换开关,采购配置并上架投入使用 目前痛点 机房有些设备单电源,无法接入UPS,停电了就无法正常工作,为…

Java-String、StringBuffer、StringBuilder区别及相关面试题

目录 一、引言二、String类的基本介绍2.1 创建String对象2.2 字符串的拼接和连接2.3 字符串的比较2.4 字符串的截取和替换2.5 字符串的查找和匹配2.6 创建格式化字符串API文档 三、StringBuffer类的基本介绍3.1 创建StringBuffer对象3.2 字符串的拼接和连接3.3 字符串的插入和删…

DDD概念以及微服务划分

目录 DDD简介&#xff1a; DDD与微服务的区别&#xff1a; DDD核心概念&#xff1a; 如何划分微服务边界&#xff1a; DDD简介&#xff1a; DDD 是 Domain-Driven Design 的缩写&#xff0c;称为领域驱动设计。它是为了解决划分业务边界的问题,是一种架构模式,也是一种划分…

Node.js安装教程,2023最新版,保姆级安装教程

Node.js安装教程2023最新版 资源准备 在官网中下载对应版本的node.js 官方连接&#xff1a;https://nodejs.org/en/download Node.js安装配置 下载完成安装包后&#xff0c;打开安装程序 配置好安装路径&#xff0c;无脑下一步至这个页面&#xff0c;这个不需要勾选 就这样…