|
序列化可序列化概念 |
const
成员
serialize
拆分为 save/load
T
才是**可序列化**的serialize
serialize
template<class Archive, class T>
inline void serialize(
Archive & ar,
T & t,
const unsigned int file_version
){
// invoke member function for class T
t.serialize(ar, file_version);
}
也就是说,模板 serialize
的默认定义假定存在以下签名的类成员函数模板
template<class Archive>
void serialize(Archive &ar, const unsigned int version){
...
}
如果未声明此类成员函数,则会发生编译时错误。为了能够调用此模板生成的成员函数将数据附加到存档,它必须是公共的,或者必须通过在类定义中包含以下内容来使序列化库可以访问该类
friend class boost::serialization::access;
应首选后一种方法,而不是将成员函数设为公共的选项。这将防止从库外部调用序列化函数。这几乎可以肯定是一个错误。不幸的是,它可能看起来可以正常工作,但以一种很难找到的方式失败。对于同一个模板如何同时用于将数据保存到存档和从存档加载数据,可能不会立即明白。关键在于,对于输出存档,&
运算符定义为 <<
,而对于输入存档,则定义为 >>
。&
的“多态”行为允许同一个模板用于保存和加载操作。这非常方便,因为它节省了大量输入,并保证类数据成员的保存和加载始终同步。这是整个序列化系统的关键。
my_class
,则重写将指定为
// namespace selection
template<class Archive>
inline void serialize(
Archive & ar,
my_class & t,
const unsigned int file_version
){
...
}
请注意,我们将此重写称为“非侵入式”。这稍微有点不准确。它并不要求类具有特殊函数,也不要求它从某个公共基类派生或进行任何其他基本设计更改。但是,它将需要访问要保存和加载的类成员。如果这些成员是 private
的,则无法序列化它们。因此,在某些情况下,即使使用这种“非侵入式”方法,也需要对要序列化的类进行细微修改。在实践中,这可能不是什么问题,因为许多库(例如 STL)公开了足够的信息,允许实现非侵入式序列化,而无需对库进行任何更改。boost::serialization
中包含任何自由函数模板和定义。如果可移植性不是问题,并且使用的编译器支持 ADL(参数依赖查找),则自由函数和模板可以在以下任何命名空间中boost::serialization
请注意,乍一看,对于实现两阶段查找的编译器来说,此建议似乎是错误的。事实上,序列化库使用了一种可能过于聪明的方法来支持此规则,即使对于此类编译器也是如此。有兴趣进一步研究此问题的读者可以在 serialization.hpp 中找到更多信息
serialize
函数的主体都必须通过将存档 operator &
依次应用于类的所有数据成员来指定要保存/加载的数据。
{
// save/load class member variables
ar & member1;
ar & member2;
}
template<class Base, class Derived>
Base & base_object(Derived &d);
该模板应用于创建对基类对象的引用,该引用可用作存档序列化运算符的参数。因此,对于**可序列化**类型 T
的类,基类状态应像这样序列化
{
// invoke serialization of the base class
ar & boost::serialization::base_object<base_class_of_T>(*this);
// save/load class member variables
ar & member1;
ar & member2;
}
抵制住将 *this
强制转换为基类的诱惑。这似乎可行,但可能无法调用正确序列化所需的代码。请注意,这与调用基类的 serialize
函数**不同**。这似乎可行,但会绕过某些用于跟踪对象、注册基类-派生类关系以及序列化系统按设计运行所需的其他簿记的代码。因此,所有 serialize
成员函数都应为 private
。
const
成员const
成员保存到存档不需要特殊考虑。可以使用 const_cast
来解决加载 const
成员的问题
ar & const_cast<T &>(t);
请注意,这违反了 const
关键字的精神和意图。const
成员在构造类实例时初始化,之后不会更改。但是,在许多情况下,这可能是最合适的。最终,这取决于在序列化上下文中 const
的含义是什么。boost::shared_ptr<T>
和 std::list<T>
的序列化定义。如果我已经为自己的类 my_t
定义了序列化,那么 std::list< boost::shared_ptr< my_t> >
的序列化已经可以使用了。有关如何为自己的类模板实现此想法的示例,请参阅 demo_auto_ptr.cpp。这显示了如何为标准库中的模板 auto_ptr
实现非侵入式序列化。
在示例 shared_ptr.hpp 中可以找到将序列化添加到标准模板中稍微棘手一些的示例
在模板的序列化规范中,通常将 serialize
拆分为 load/save
对。请注意,上文中描述的便捷宏在这些情况下没有帮助,因为模板类参数的数量和类型与为简单类拆分 serialize
时使用的参数不匹配。请改用重写语法。
{
// invoke serialization of the base class
ar & boost::serialization::base_object<base_class_of_T>(*this);
// save/load class member variables
ar & member1;
ar & member2;
// if its a recent version of the class
if(1 < file_version)
// save load recently added class members
ar & member3;
}
serialize
拆分为 Save/Load
template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
// invoke serialization of the base class
ar << boost::serialization::base_object<const base_class_of_T>(*this);
ar << member1;
ar << member2;
ar << member3;
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
// invoke serialization of the base class
ar >> boost::serialization::base_object<base_class_of_T>(*this);
ar >> member1;
ar >> member2;
if(version > 0)
ar >> member3;
}
template<class Archive>
void serialize(
Archive & ar,
const unsigned int file_version
){
boost::serialization::split_member(ar, *this, file_version);
}
这将序列化拆分为两个独立的函数 save
和 load
。由于新的 serialize
模板始终相同,因此可以通过调用头文件 boost/serialization/split_member.hpp 中定义的宏 BOOST_SERIALIZATION_SPLIT_MEMBER() 来生成它。因此,上面的整个 serialize
函数可以用以下内容替换
BOOST_SERIALIZATION_SPLIT_MEMBER()
serialize
函数模板进行的非侵入式序列化,情况也是如此。 要使用 save
和 load
函数模板而不是 serialize
: namespace boost { namespace serialization { template<class Archive> void save(Archive & ar, const my_class & t, unsigned int version) { ... } template<class Archive> void load(Archive & ar, my_class & t, unsigned int version) { ... } }}
包含头文件 boost/serialization/split_free.hpp 并重写免费的 serialize
函数模板
namespace boost { namespace serialization {
template<class Archive>
inline void serialize(
Archive & ar,
my_class & t,
const unsigned int file_version
){
split_free(ar, t, file_version);
}
}}
为了缩短输入,上述模板可以用宏来代替
BOOST_SERIALIZATION_SPLIT_FREE(my_class)
请注意,尽管提供了将 serialize
函数拆分为 save/load
的功能,但最好使用带有相应 &
运算符的 serialize
函数。序列化实现的关键在于对象的保存和加载顺序完全相同。使用 &
运算符和 serialize
函数可以确保始终如此,并将最大限度减少与 save
和 load
函数同步相关的难以发现的错误。还要注意,BOOST_SERIALIZATION_SPLIT_FREE
必须在任何命名空间之外使用。
要通过指针正确保存和恢复对象,必须解决以下情况
保存指针
<<
和 >>
运算符序列化多少次
// 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();
}
默认的 load_construct_data
调用默认构造函数“就地”初始化内存。如果没有这样的默认构造函数,则必须重写函数模板 load_construct_data
,可能还要重写 save_construct_data
。下面是一个简单的例子
class my_class {
private:
friend class boost::serialization::access;
const int m_attribute; // some immutable aspect of the instance
int m_state; // mutable state of this instance
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version){
ar & m_state;
}
public:
// no default construct guarantees that no invalid object
// ever exists
my_class(int attribute) :
m_attribute(attribute),
m_state(0)
{}
};
重写将是
namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
Archive & ar, const my_class * t, const unsigned int file_version
){
// save data required to construct instance
ar << t->m_attribute;
}
template<class Archive>
inline void load_construct_data(
Archive & ar, my_class * t, const unsigned int file_version
){
// retrieve data from archive required to construct new instance
int attribute;
ar >> attribute;
// invoke inplace constructor to initialize instance of my_class
::new(t)my_class(attribute);
}
}} // namespace ...
除了指针的反序列化之外,这些重写还用于其元素类型没有默认构造函数的 STL 容器的反序列化。
class base {
...
};
class derived_one : public base {
...
};
class derived_two : public base {
...
};
int main(){
...
base *b;
...
ar & b;
}
保存 b
时应该保存哪种对象?加载 b
时应该创建哪种对象?它应该是类 derived_one
、derived_two
还是 base
的对象?事实证明,序列化的对象类型取决于基类(在本例中为 base
)是否是多态的。如果 base
不是多态的,也就是说,如果它没有虚函数,则将序列化类型 base
的对象。任何派生类中的信息都将丢失。如果这就是期望的结果(通常不是),则无需其他操作。
如果基类是多态的,则将序列化最派生类型(在本例中为 derived_one
或 derived_two
)的对象。要序列化哪种类型的对象的问题(几乎)由库自动处理。
系统在第一次序列化该类的对象时,会在存档中“注册”每个类,并为其分配一个序列号。下次在同一个存档中序列化该类的对象时,该数字将写入存档中。因此,每个类在存档中都被唯一标识。当读回存档时,每个新的序列号都会与正在读取的类重新关联。请注意,这意味着在保存和加载期间都必须进行“注册”,以便在加载时构建的类整数表与在保存时构建的类整数表相同。事实上,整个序列化系统的关键是事物总是以相同的顺序保存和加载。这包括“注册”。
扩展我们之前的例子
int main(){
derived_one d1;
derived_two d2:
...
ar & d1;
ar & d2;
// A side effect of serialization of objects d1 and d2 is that
// the classes derived_one and derived_two become known to the archive.
// So subsequent serialization of those classes by base pointer works
// without any special considerations.
base *b;
...
ar & b;
}
当读取 b
时,它前面有一个唯一的(对于存档)类标识符,该标识符先前已与类 derived_one
或 derived_two
相关联。如果派生类尚未按上述方式自动“注册”,则在调用序列化时将抛出 unregistered_class
异常。
这可以通过显式注册派生类来解决。所有存档都派生自一个基类,该基类实现以下模板
template<class T>
register_type(T * = NULL);
所以我们的问题也可以通过编写以下代码来解决
int main(){
...
ar.template register_type<derived_one>();
ar.template register_type<derived_two>();
base *b;
...
ar & b;
}
请注意,如果序列化函数在保存和加载之间拆分,则两个函数都必须包含注册。这是为了保持保存和相应的加载同步所必需的。所以我们还有另一种方法
#include <boost/serialization/export.hpp>
...
BOOST_CLASS_EXPORT_GUID(derived_one, "derived_one")
BOOST_CLASS_EXPORT_GUID(derived_two, "derived_two")
int main(){
...
base *b;
...
ar & b;
}
宏 BOOST_CLASS_EXPORT_GUID
将字符串字面量与类相关联。在上面的例子中,我们使用了类名的字符串呈现。如果通过指针序列化此类“导出”类的对象,并且该对象未注册,则“导出”字符串将包含在存档中。稍后读取存档时,将使用字符串字面量来查找应该由序列化库创建的类。这允许每个类与它的字符串标识符一起放在一个单独的头文件中。不需要维护可能被序列化的派生类的单独“预注册”。这种注册方法称为“密钥导出”。有关此主题的更多信息,请参见“类特征 - 导出密钥”一节。
template<class Archive, class T>
形式的模板化函数。这意味着必须为程序中序列化的每个存档和数据类型组合实例化序列化代码。程序可能永远不会显式引用派生类的多态指针,因此通常永远不会实例化序列化此类类的代码。因此,除了在存档中包含导出密钥字符串之外,BOOST_CLASS_EXPORT_GUID
还为程序使用的所有存档类显式实例化类序列化代码。
track_selectively
。也就是说,当且仅当对象通过程序中任何位置的指针序列化时才跟踪它们。通过上述任何方式“注册”的任何对象都被假定为通过程序中某个位置的指针序列化,因此将被跟踪。在某些情况下,这可能会导致效率低下。假设我们有一个供多个程序使用的类模块。因为某些程序序列化了指向此类对象的指针,所以我们在类头文件中指定 BOOST_CLASS_EXPORT
来导出 类标识符。当另一个程序包含此模块时,将始终跟踪此类的对象,即使可能不需要这样做。这种情况可以通过在这些程序中使用 track_never
来解决。也可能发生这样的情况:即使程序通过指针进行序列化,我们更关心的是效率,而不是避免创建重复对象的可能性。可能是我们碰巧知道不会有重复项。也可能是创建一些重复项是良性的,考虑到跟踪重复项的运行时成本,不值得避免。同样,可以使用 track_never
。
boost::serialization::base_object<Base>(Derived &)
序列化基类对象的一个副作用是,确保在进入 main
函数之前将基类/派生类对添加到表中。这非常方便,并且可以使语法更加简洁。唯一的问题是,当通过指针序列化派生类,而不需要调用其基类序列化时,就会发生这种情况。在这种情况下,有两种选择。显而易见的一种是使用 base_object
调用基类序列化,并为基类序列化指定一个空函数。另一种方法是通过调用模板 void_cast_register<Derived, Base>();
来显式“注册”基类/派生类关系。请注意,这里使用的术语“注册”与其在前一节中的用法无关。下面是如何做到这一点的示例
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
class base {
friend class boost::serialization::access;
//...
// only required when using method 1 below
// no real serialization required - specify a vestigial one
template<class Archive>
void serialize(Archive & ar, const unsigned int file_version){}
};
class derived : public base {
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int file_version){
// method 1 : invoke base class serialization
ar & boost::serialization::base_object<base>(*this);
// method 2 : explicitly register base/derived relationship
boost::serialization::void_cast_register<derived, base>(
static_cast<derived *>(NULL),
static_cast<base *>(NULL)
)
}
};
BOOST_CLASS_EXPORT_GUID(derived, "derived")
int main(){
//...
std::stringstream ss;
boost::archive::text_iarchive ar(ss);
base *b;
ar >> b;
}
为了使该模板能够在由不符合标准的编译器编译的代码中被调用,可以使用以下语法
boost::serialization::void_cast_register(
static_cast<Derived *>(NULL),
static_cast<Base *>(NULL)
);
有关详细信息,请参阅 模板调用语法
class object;
class my_class {
private:
friend class boost::serialization::access;
int member1;
object & member2;
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version);
public:
my_class(int m, object & o) :
member1(m),
member2(o)
{}
};
重写将是
namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
Archive & ar, const my_class * t, const unsigned int file_version
){
// save data required to construct instance
ar << t.member1;
// serialize reference to object as a pointer
ar << & t.member2;
}
template<class Archive>
inline void load_construct_data(
Archive & ar, my_class * t, const unsigned int file_version
){
// retrieve data from archive required to construct new instance
int m;
ar >> m;
// create and load data through pointer to object
// tracking handles issues of duplicates.
object * optr;
ar >> optr;
// invoke inplace constructor to initialize instance of my_class
::new(t)my_class(m, *optr);
}
}} // namespace ...
T
是可序列化类型,则任何类型为 T 的原生 C++ 数组都是可序列化类型。也就是说,如果 T
是可序列化类型,则以下内容将自动可用并按预期工作
T t[4];
ar << t;
...
ar >> t;
std::list
使用包含的序列化代码,请使用
#include <boost/serialization/list.hpp>
而不是
#include <list>
由于前者包含后者,因此只需这样做即可。这同样适用于所有 STL 集合以及支持它们所需的模板(例如 std::pair
)。截至撰写本文时,该库包含以下 boost 类的序列化
std::variant 也受支持
。其他类正在添加到列表中,因此请查看 boost 文件部分和标题以获取新的实现!© Copyright Robert Ramey 2002-2004。在 Boost 软件许可证 1.0 版下分发。(请参阅随附文件 LICENSE_1_0.txt 或访问 https://boost.ac.cn/LICENSE_1_0.txt 获取副本)