Serialization - Class Serialization Traits

###  | # Serialization ## Class Serialization Traits

Version
Implementation Level
Object Tracking
Export Key
Abstract
Type Information Implementation
Template Serialization Traits

Serialization of data depends on the type of the data. For example, for primitive types such as an `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.

### Version

This header file includes the following code:

    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.

### Implementation Level

In the same manner as the above, the "level" of implementation of serialization is specified. The header file level.hpp defines the following.

    // 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.

- if the data type is `volatile` assign `not_serializable`
- else if it's an enum or fundamental type assign `primitive_type`
- else assign `object_class_info`

That is, for most user defined types, objects will be serialized along with class version information. This will permit one to maintain backward compatibility with archives which contain previous versions. However, with this ability comes a small runtime cost. For types whose definition will "never" change, efficiency can be gained by specifying `object_serializable` to override the default setting of `object_class_info`. For example, this has been done for the binary_object wrapper

### Object Tracking

Depending on the way a type is used, it may be necessary or convenient to track the address of objects saved and loaded. For example, this is generally necessary while serializing objects through a pointer in order to be sure that multiple identical objects are not created when an archive is loaded. This "tracking behavior" is controlled by the type trait defined in the header file tracking.hpp which defines the following:

    // 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:

- For primitive, `track_never`.
- For pointers, `track_never`. That is, addresses of addresses are not tracked by default.
- All current serialization wrappers such as `boost::serialization::nvp`, `track_never`.
- For all other types, `track_selectivly`. That is addresses of serialized objects are tracked if and only if one or more of the following is true:
  - an object of this type is anywhere in the program serialized through a pointer.
  - the class is explicitly "exported" - see below.
  - the class is explicitly "registered" in the archive

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.

### Export Key

When serializing a derived class through a base class pointer, it may be convenient to define an external name by which the derived class can be identified. (Elsewhere in this manual, the serialization of derived classes is addressed in detail.) Standard C++ does implement `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:

- There is no guarantee that the string is the same across platforms. This would then fail to support portable archives.
- In using code modules from various sources, classes may have to be wrapped in different namespaces in different programs.
- There might be classes locally defined in different code modules that have the same name.
- There might be classes with different names that we want to consider equivalent for purposes of of serialization.

So the header file export.hpp includes macro definitions to specify the external string used to identify the class. (GUID stands for Globally Unique IDentfier.)

    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.

### Abstract

When serializing an object through a pointer to its base class and that base class is abstract (i.e. has at least one virtual function assigned a value of 0), A compile error will be emitted. This is addressable in one over several ways:

- remove the =0 in the base classes so that the base class is no longer abstract.
- implement is_abstract for your compiler. (code written according to the C++ standard is included with this library. But it is known to fail on several compilers.
- use the macro `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.

### Type Information Implementation

This last trait is also related to the serialization of objects through a base class pointer. The implementation of this facility requires the ability to determine at run time the true type of the object that a base class pointer points to. Different serialization systems do this in different ways. In our system, the default method is to use the function `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(
        derived_class,
        extended_type_info_no_rtti<base_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.

### Template Serialization Traits

In some instances it might be convenient to assign serialization traits to a whole group of classes at once. Consider, the name-value pair wrapper

    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)
    >
    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 primitive_type object_serializable object_class_info` | none
`Tracking` | tracking level | `track_never track_selectivly track_always` | none
`Version` | `class version` | unsigned integer | `0`
`ETTI` | `type_info` implementation | `extended_type_info_typeid extended_type_info_no_rtti` | default `type_info implementation`

© Copyright Robert Ramey 2002-2004. 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)