Serializing classes

Support for serializing and deserializing objects is implemented by adding a serialize() template method to the class:

class hms {

public:

// ...

    template<typename ptr_type, typename iter_type>
    static void serialize(ptr_type ptr, iter_type &i)
    {
        i(ptr->h);
        i(ptr->m);
	i(ptr->s);
    }
};

This is an abbreviated excerpt from the actual definition of the x::hms class that holds a interval of time expressed as hours, minutes, and seconds. Defining the serialize() template function makes this class serializable. To support serialization of containers of the serializable class, the class must also implement a default constructor and a copy constructor (explicitly or implicitly).

The serialize() template method receives two parameters:

  1. A mutable or a constant pointer to the object getting deserialized or serialized. A mutable pointer gets passed in when deserialization the object. A const pointer gets passed on when serializing the object.

    The above serialize()'s ptr_type is always either hms * or const hms *.

  2. When serializing, serialize() receives an instance of a serializing iterator. When deserializing, serialize() receives an instance of a deserializing iterator.

The serialize() template method typically iterates over the same class members, in the same order, using the iterator, automatically implementing identical serializing and deserializing logic.

Still, sometimes different logic is necessary when serializing and deserilizing. Here's the serialize() method from the x::tzfile class:

template<typename ptr_type, typename iter_type>
static void serialize(ptr_type, iter_type &i)
{
    i(name, 255);

    if constexpr (!i.serializing)
    {
        std::string namecpy=ptr->name;
        ptr->load_file(namecpy);
    }
}

x::tzfile contains detailed data structures that implement the mapping between epoch time and local time. This mapping is loaded from the system timezone database, and consists of a collection of arrays of various objects, and other boring data.

It's possible to simply serialize each one of the internal class members. However, since a serialized object may only be deserialized into a structurally compatible object, changing anything about the internal x::tzfile class members ends up invalidating all the previously serialized objects.

An arbitrary timezone is defined solely by the timezone's name, which is a single string. Therefore, x::tzfile is serialized simply by serializing its name. That means only the timezone's name gets deserialized (enforcing the maximum timezone name size of 255 characters), so the timezone definition must be loaded from the system timezone database also. This is what happens in the given example.

iterator.serializing is a constant expression that evaluates to true if the parameter to serialize() is the serializing iterator, and false if it's the deserializing iterator. First, the timezone mapping name gets either serialized or deserialized. When serializing, the process is finished at this point. When deserializing the object, load_file() gets invoked. This is an internal class method that loads the given timezone mapping from the system database.

The tradeoff here is that a serializing timezone mapping can only be truly deserialized if an identical timezone mapping exists at the time and place when deserializing occurs. This is a reasonable assumption. Although serializing the internal structure of x::tzfile is possible, as previously mentioned, the added imposition of prohibiting any future changes to the inner structure of this class is weighed to be too high of a price to pay for the ability to deserialize a timezone mapping in an environment that does not have an identical mapping available. This is a rare possibility, so the value added is very small.

(The reason for making a copy of the timezone mapping when deserializing is because load_file takes a reference to the timezone mapping as a parameter, and assigns the referenced string object to the name class member).