|
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_selectively
.
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.
This is addressed by invoking
BOOST_CLASS_EXPORT_IMPLEMENT(T)
in the file which defines (implements) the class T.
This ensures that code for the derived class T will
be explicity instantiated.
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:
So in the serialization library, this is addressed by invoking
BOOST_CLASS_EXPORT_KEY2(my_class, "my_class_external_identifier")
in the header file which declares the class.
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_KEY(my_class)
.
BOOST_CLASS_EXPORT(my_class)
or
BOOST_CLASS_EXPORT_GUID(my_class, "my_class_external_identifier")
in either the declaration header or definition. These macros
expand to invocation of both of the macros described above.
(GUID stands for Globally Unique IDentfier.)
(Elsewhere in this manual, the serialization of derived classes is addressed in detail.)
The header file export.hpp contains all macro definitions described here. The library will throw a runtime exception if
BOOST_IS_ABSTRACT(T)
to do this. Not all compilers support this type trait and corresponding
macro. To address this, the macro
BOOST_SERIALIZATION_ASSUME_ABSTRACT(T)
has been
implemented to permit one to explicitly indicate that a specified
type is in fact abstract. This will guarentee that
BOOST_IS_ABSTRACT
will return the correct value for all compilers.
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.
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.
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)
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 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_ |
T t;
ar << t;
unless the tracking_level serialization trait is set to "track_never". The following
will compile without problem:
const T t
ar << t;
Likewise, the following code will trap at compile time:
T * t;
ar >> t;
if the tracking_level serialization trait is set to "track_never".
The following case illustrates the function of this message.
It was originally used as an example in the
mailing list by Peter Dimov.
Suppose that the above message is not displayed and the code is used as is.
class construct_from
{
...
};
void main(){
...
Y y;
construct_from x(y);
ar << x;
}
void main(){
...
Y y;
construct_from x(y);
ar << x;
...
x.f(); // change x in some way
...
ar << x
}
Again no problem. He gets two different of copies in the archive, each one is different. That is he gets exactly what he expects and is naturally delighted.
class K {
shared_ptr <construct_from> z;
template <class Archive>
void serialize(Archive & ar, const unsigned version){
ar << z;
}
};
He builds and runs the program and tests his new functionality. It works great and he's delighted.
BOOST_CLASS_TRACKING(construct_from, track_never)
shared_ptr<construct_from>
is not going to have a single raw pointer shared amongst the instances. Each loaded
shared_ptr<construct_from>
is going to
have its own distinct raw pointer. This will break
shared_ptr
and cause a memory leak. Again,
The cause of this problem is very far removed from the point of discovery. It could
well be that the problem is not even discovered until after the archives are loaded.
Now we not only have a difficult to find and fix program bug, but we have a bunch of
invalid archives and lost data.
Now consider what happens when the message is displayed:
ar << x;
const_cast
- because it looks bad. So he'll just make the following change an move on.
Y y;
const construct_from x(y);
ar << x;
Things work fine and he moves on.
Y y;
const construct_from x(y);
...
x.f(); // change x in some way ; compile error f() is not const
...
ar << x
He's mildly annoyed now he tries the following:
const
from const construct_from
above - damn now he
gets the trap. If he looks at the comment code where the
BOOST_STATIC_ASSERT
occurs, he'll do one of two things
const_cast
and fire off a complaint to the list and mabe they will fix it.
In this case, the story branches off to the previous scenario.
BOOST_CLASS_TRACKING(construct_from, track_never)
construct_from
trait has been set to
"track_never" so he should always get copies and the log should be what we expect.
BOOST_CLASS_TRACKING
that
was inserted. Now the second trap is avoided, But damn - the first trap is
popping up again. Eventually, after some code restructuring, the differing
requirements of serializating construct_from
are reconciled.
A problem arises when a program which reads an archive includes the operation ar >> T * so that tracking information will be included in the archive. When a program which creates the archive doesn't include ar << T it is presumed that the archive doesn't include tracking information and the archive will fail to load. Also the reverse situation could trigger a similar problem.
Though this situation is unlikely for several reasones, it is possible - hence this warning.
© 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)