Serializing polymorphic classes with environment-specific state

75 views Asked by At

I have some classes that look roughly like this:

class Base {
public:
    virtual ~Base() {}
    ...
};

class Derived1 {
    OuterState1& outerState;
    InnerState1 innerState;
    ...
    template <typename Ar>
    void serialize(Ar& ar, const unsigned int /*version*/) {
        ar & innerState;
    }
public:
    Derived1(OuterState1& outerState) : outerState(outerState) {}
    ...
};

class Derived2 {
    OuterState2& outerState;
    InnerState2 innerState;
    ...
    template <typename Ar>
    void serialize(Ar& ar, const unsigned int /*version*/) {
        ar & innerState;
    }
public:
    Derived1(OuterState2& outerState) : outerState(outerState) {}
    ...
};

Basically, the classes have a state that depends on the outside environment, and I don't want to serialize it. This state may be different for the different subclasses. I want to serialize this class. A good thing is that boost::serialization handles polymorphic classes well, but it seems to me that not well enough for me. I could find the following ways to serialize these objects, neither which I like:

  1. Use global variables for the outer state. Now either we use these global variables inside the class, or overload load_construct_data() and create the objects from this global variable. The problem with this solution is that it requires global variables, which is generally a bad design, and breaks if the program needs to handle more than one such states.

  2. Do not use the polymorphic serialization feature of Boost. Instead, save the actual type in an enum, then save the object non-polymorphically. When loading, load the type enum, then in a switch create the object of the appropriate type with the appropriate outer state, then load the object non-polymorphically. The problem with this is that I have to do a lot of manual coding that would automagically be done by Boost, and that it doesn't work if I want to serialize a collection of such objects.

Is there any better, more elegant solution for this problem?

1

There are 1 answers

0
petersohn On BEST ANSWER

The solution is to save the environment-specific state as a pointer in the class. Then save/load it in the archive before the actual classes, and modify its value. So serialization of the base class looks something like this:

class Derived1 {
    OuterState1& outerState;
    InnerState1 innerState;
    ...
    template <typename Ar>
    void serialize(Ar& ar, const unsigned int /*version*/) {
        ar & innerState;
    }
public:
    Derived1(OuterState1& outerState) : outerState(outerState) {}
    const OuterState1& getOuterState() const { return outerState; }
    ...
};

    template <typename Ar>
    void save_construct_data(Ar& ar, const Derived1* object, const unsigned int /*version*/) {
        ar << &object.getOuterState();
    }

    template <typename Ar>
    void load_construct_data(Ar& ar, Derived1* object, const unsigned int /*version*/) {
        OuterState1* state;
        ar >> state;
        ::new(object)Derived1{*state};
    }

Make serialization of the environment specific object a NOP.

template <typename Ar>
void serialize(Ar&, OuterState1&, const unsigned int) {
}

When saving, do it like this:

OuterState1 outerState1;
OuterState2 outerState2;
std::shared_ptr<Base> object = getObject();
...
// saving starts now
archive << outerState1 << outerState2 << object;
...

When loading, load the states, then override them. Suppose outerState1 and outerState2 are located at the same position as before, and already has the new, desired value.

std::shared_ptr<Base> object;
archive >> outerState1 >> outerState2 >> object;

On another note, the code presented in the question is not complete. The base class serialization needs to be done, but that's beside the point now.