|
SerializationClass Serialization Traits |
int
, it wouldn't make sense to save
a version number in the archive. Likewise, for a data type that is never
serialized through a pointer, it would (almost) never make sense to track
the address of objects saved to/loaded from the archive as it will never
be saved/loaded more than once in any case. Details of
serialization for a particular data type will vary depending on the
type, the way it is used and specifications of the programmer.
One can alter the manner in which a particular data type is serialized by specifying one or more class serialization traits. It is not generally necessary for the programmer to explictly assign traits to his classes as there are default values for all traits. If the default values are not appropriate they can be assigned by the programmer. A template is used to associate a typename with a constant. For example see version.hpp.
namespace boost {
namespace serialization {
template<class T>
struct version
{
BOOST_STATIC_CONSTANT(unsigned int, value = 0);
};
} // namespace serialization
} // namespace boost
For any class T
, The default definition
of boost::serialization::version<T>::value
is 0.
If we want to assign a value of 2 as the version for class my_class
we specialize the version template:
namespace boost {
namespace serialization {
struct version<my_class>
{
BOOST_STATIC_CONSTANT(unsigned int, value = 2);
};
} // namespace serialization
} // namespace boost
Now whenever the version number for class my_class
is required,
the value 2 will be returned rather than the default value of 0.
To diminish typing and enhance readability, a macro is defined so that instead of the above, we could write:
BOOST_CLASS_VERSION(my_class, 2)
which expands to the code above.
// names for each level
enum level_type
{
// Don't serialize this type. An attempt to do so should
// invoke a compile time assertion.
not_serializable = 0,
// write/read this type directly to the archive. In this case
// serialization code won't be called. This is the default
// case for fundamental types. It presumes a member function or
// template in the archive class that can handle this type.
// there is no runtime overhead associated reading/writing
// instances of this level
primitive_type = 1,
// Serialize the objects of this type using the objects "serialize"
// function or template. This permits values to be written/read
// to/from archives but includes no class or version information.
object_serializable = 2,
///////////////////////////////////////////////////////////////////
// once an object is serialized at one of the above levels, the
// corresponding archives cannot be read if the implementation level
// for the archive object is changed.
///////////////////////////////////////////////////////////////////
// Add class information to the archive. Class information includes
// implementation level, class version and class name if available.
object_class_info = 3,
};
Using a macro defined in level.hpp
we can specify
that my_class
should be serialized along with its version number:
BOOST_CLASS_IMPLEMENTATION(my_class, boost::serialization::object_class_info)
If implementation level is not explicitly assigned, the system uses
a default according to the following rules.
volatile
assign not_serializable
primitive_type
object_class_info
object_serializable
to override the default setting of object_class_info
.
For example,
this has been done for the
binary_object wrapper
// names for each tracking level
enum tracking_type
{
// never track this type
track_never = 0,
// track objects of this type if the object is serialized through a
// pointer.
track_selectively = 1,
// always track this type
track_always = 2
};
A corresponding macro is defined so that we can use:
BOOST_CLASS_TRACKING(my_class, boost::serialization::track_never)
Default tracking traits are:
track_never
.
track_never
.
That is, addresses of addresses are not tracked by default.
boost::serialization::nvp
,
track_never
.
track_selectivly
.
That is addresses of serialized objects are tracked if and only if
one or more of the following is true:
The default behavior is almost always the most convenient one. However, there a few cases where it would be desirable to override the default. One case is that of a virtual base class. In a diamond heritance structure with a virtual base class, object tracking will prevent redundant save/load invocations. So here is one case where it might be convenient to override the default tracking trait. (Note: in a future version the default will be reimplemented to automatically track classes used as virtual bases). This situation is demonstrated by test_diamond.cpp included with the library.
typeid()
which can be
used to return a unique string for the class. This is not entirely
statisfactory for our purposes for the following reasons:
BOOST_CLASS_EXPORT_GUID(my_class, "my_class_external_identifier")
In a large majority of applications, the class name works just fine
for the external identifier string so the following short cut is
defined
BOOST_CLASS_EXPORT(my_class)
which expands to:
BOOST_CLASS_EXPORT_GUID(my_class, "my_class")
If the an external name is required somewhere in the program and none
has been assigned, a static assertion will be invoked.
BOOST_IS_ABSTRACT(my_class)
to indicate
that the class is an abstract base class. This will cause the compiler
to avoid generating code that causes this error.
This macro must be used in the global namespace, with full namespace
qualification of the argument class.
typeid(...)
which is available
in systems which support RTTI (Run Time
Type Information).
This will be satisfactory in almost all cases and most users of this
library will lose nothing in skipping this section of the manual.
However, there are some cases where the default type determination
system is not convenient. Some platforms might not support
RTTI or it may have been disabled in order to speed execution
or for some other reason. Some applications, E.G. runtime linking
of plug-in modules, can't depend on C++ RTTI to determine the
true derived class. RTTI only returns the correct type for polymorphic
classes - classes with at least one virtual function. If any of these
situations applies, one may substitute his own implementation of
extended_type_info
The interface to facilities required to implement serialization is defined in
extended_type_info.hpp.
Default implementation of these facilities based on typeid(...)
is defined in
extended_type_info_typeid.hpp.
An alternative implementation based on exported class identifiers
is defined in
extended_type_info_no_rtti.hpp.
By invoking the macro:
BOOST_CLASS_TYPE_INFO(
my_class,
extended_type_info_no_rtti<my_class>
)
we can assign the type information implementation to each class on a case by
case basis. There is no requirement that all classes in a program use the same
implementation of extended_type_info
. This supports the concept
that serialization of each class is specified "once and for all" in a header
file that can be included in any project without change.
This is illustrated by the test program test_no_rtti.cpp. Other implementations are possible and might be necessary for certain special cases. version.hpp.
namespace boost {
namespace serialization {
template<class T>
struct is_wrapper
: public mpl::false_
{};
} // namespace serialization
} // namespace boost
For any class T
, The default definition
of boost::serialization::is_wrapper<T>::value
is thus false.
If we want to declare that a class my_class
is a wrapper we specialize the version template:
namespace boost {
namespace serialization {
struct is_wrapper<my_class>
: mpl::true_
{};
} // namespace serialization
} // namespace boost
To diminish typing and enhance readability, a macro is defined so that instead of the above, we could write:
BOOST_CLASS_IS_WRAPPER(my_class)
which expands to the code above.
template<class T>
struct nvp : public std::pair<const char *, T *>
{
...
};
used by XML archives to associate a name with a data variable of type T.
These data types are never tracked and never versioned. So one might
want to specify:
BOOST_CLASS_IMPLEMENTATION(nvp<T>, boost::serialization::level_type::object_serializable)
BOOST_CLASS_TRACKING(nvp<T>, boost::serialization::track_never)
Examination of the definition of these macros reveals that they won't expand
to sensible code when used with a template argument. So rather than using the
convenience macros, use the original definitions
template<class T>
struct implementation_level<nvp<T> >
{
typedef mpl::integral_c_tag tag;
typedef mpl::int_<object_serializable> type;
BOOST_STATIC_CONSTANT(
int,
value = implementation_level::type::value
);
};
// nvp objects are generally created on the stack and are never tracked
template<class T>
struct tracking_level<nvp<T> >
{
typedef mpl::integral_c_tag tag;
typedef mpl::int_<track_never> type;
BOOST_STATIC_CONSTANT(
int,
value = tracking_level::type::value
);
};
to assign serialization traits to all classes generated by the template
nvp<T>
Note that it is only possible to use the above method to assign traits to templates when using compilers which correctly support Partial Template Specialization. One's first impulse might be to do something like:
#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
template<class T>
struct implementation_level<nvp<T> >
{
... // see above
};
// nvp objects are generally created on the stack and are never tracked
template<class T>
struct tracking_level<nvp<T> >
{
... // see above
};
#endif
This can be problematic when one wants to make his code and archives
portable to other platforms. It means the she objects will be serialized differently
depending on the platform used. This implies that objects saved from one platform
won't be loaded properly on another. In other words, archives won't be portable.
This problem is addressed by creating another method of assigning serialization traits to user classes. This is illustrated by the serialization for a name-value pair.
Specifically, this entails deriving the template from a special class
boost::serialization::traits
which is specialized for a specific
combination of serialization traits.
When looking up the serialization traits, the library first checks to see if this class has been
used as a base class. If so, the corresponding traits are used. Otherwise, the standard defaults
are used. By deriving from a serialization traits class rather than relying upon Partial Template
Specializaton, one can a apply serialization traits to a template and those traits will be
the same across all known platforms.
The signature for the traits template is:
template<
class T,
int Level,
int Tracking,
unsigned int Version = 0,
class ETII = BOOST_SERIALIZATION_DEFAULT_TYPE_INFO(T),
class IsWrapper = mpl::false_
>
struct traits
and template parameters should be assigned according to the following table:
parameter | description | permitted values | default value |
---|---|---|---|
T | target class | class name | none |
Level | implementation level | not_serializable | none |
Tracking | tracking level | track_never | none |
Version | class version | unsigned integer | 0 |
ETTI | type_info implementation | extended_type_info_typeid | default type_info implementation |
IsWrapper | is the type a wrapper? | mpl::false_ | mpl::false_ |
namespace boost { namespace serialization {
template
struct is_bitwise_serializable
: public is_arithmetic
{};
} }
is used, and can be specialized for other classes. The specialization
is made easy by the corresponding macro:
BOOST_IS_BITWISE_SERIALIZABLE(my_class)
© Copyright Robert Ramey 2002-2004 and Matthias Troyer 2006. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)