boost::serialization 对于私有构造函数的序列化
这两天在工作中遇到一个需求,需要将C++类序列化成XML数据文件。
熟悉C#或者Java的都应该清楚,因为有元数据和框架内置的Serialization模块的存在,将一个类的实例序列化成XML文件是相当容易的。但是C++因为没有标准库支持序列化,我转而借助boost::serialization的帮助。
boost::serialization 相当强大,一般的C++类都支持,对于STL里面的集合类型(list, vector等)也都能够很好的序列化。具体的使用方法可以查看boost的文档,应该上手还是很简单的:http://www.boost.org/doc/libs/1_39_0/libs/serialization/doc/index.html
然而现实的需求肯定不是这些基础的教程能够完全覆盖的,我就遇到了关于私有缺省构造函数的类的序列化问题。
故事是这样的:
我们项目中有一套消息接口,其中有一套预定义好的消息数据类型,举个例子:SomeMarketData,用来传输某个市场的数据。
class SomeMarketData
{
public:
SomeMarketData* CreateInstance(void) { return new SomeMarketData; }
private:
SomeMarketData(void);
SomeMarketData(SomeMarketData const& aData);
SomeMarketData& operator =(SomeMarketData const& aData);
}
这个类的缺省构造,拷贝构造和赋值操作符都被定义为私有。我们项目中的需求是将这个类的集合类型std::vector<SomeMarketData*>序列化。boost::serialization支持将vector的指针元素序列化和反序列化的能力,同时对于私有构造函数也提供了一定的支持,boost的文档中提到:Boost Doc -> Serialization -> Reference -> Serialization Concept -> Pointers -> Non-default Constructors http://www.boost.org/doc/libs/1_39_0/libs/serialization/doc/index.html
如果你的类的缺省构造函数是私有的,而只能通过其他有参数的构造函数来进行构造,那么可以用特化(重载)load_construct_data这个方法来改变boost框架中内置的对象构造过程,如下:
// load data required for construction and invoke constructor in place
template<class Archive, class T>
inline void load_construct_data(Archive & ar, T * t, const unsigned int file_version){
// default just uses the default constructor to initialize
// previously allocated memory.
::new(t)T();
}
我们的问题是,SomeMarketData并不存在任何的public的构造函数,无论是缺省还是有参数的。只有一个CreateInstance这个工厂方法可以创建这个类的实例。我尝试在load_construct_data中调用CreateInstance方法,发现并不奏效,实例t的虚表在退出这个函数之后就完全消失了,或者说就根本没有被正确构造。
眼尖的你肯定应该已经发现了,load_construct_data函数接受一个T(这里就是我们的SomeMarketData)的指针类型做参数,t被传入的时候,实际上是做一次指针的拷贝操作,除了::new操作符,其他的操作都无法更改t所指向的对象,也就是说我可以在这个函数内部调用 t = SomeMarketData::CreateInstance(); 来实例化t,但是退出以后,这个t并没有被传递出去,更严重的是,t在函数内实例化出来的对象反而泄漏掉了。
通过对boost::serialization库源码的研究,当然整个过程的复杂和痛苦就不详述了,我发现在重载了load_construct_data函数后,当有指针类型需要被反序列化构造的时候,boost会调用boost/archive/detail/iserializer.hpp 里面的 heap_allocator结构来实例化对象,其中的invoke方法就是我要寻找的。
找到了问题点,解决起来其实就很简单了,针对每种需要序列化的类型,特化(重载)一个heap_allocator出来就可以了,针对我们的SomeMarketData类,我可以这样写:
namespace boost { namespace archive { namespace detail { // 命名空间很重要
template<>
struct heap_allocator
{
static SomeMarketData * invoke(){ return SomeMarketData::CreateInstance();
}
};
}}}
上述代码和load_construct_data的特化写在一起就可以了。

