C ++ serialization with polymorphism and prototypes

It has long been interested in the topic of serialization, and specifically, the serialization of objects stored by pointer to the base class. For example, if we want to load the application interface from a file, then most likely we will have to fill a container with the type “std :: vector” with polymorphic objects" The question is how to implement this. I recently decided to do this and that's what happened.

To begin with, I suggested that we still have to inherit the iSerializable interface in the base class, of this kind:

class iSerializable
    virtual void serialize (Node node) = 0;

And the final class should look something like this:

class ConcreteClass : public iSerializable
    virtual void serialize (Node node) override
        node.set_name ("BaseClass");
        node.serialize (m_int, "int");
        node.serialize (m_uint, "uint");
    int m_int = 10;
    unsigned int m_uint = 200;

The Node class must, in this case, implement the processing of the object using an XML parser. For parsing, I took pugixml. Node contains the field:
xml_node m_node;

template of the function of the host object and its name:
template  void serialize (T& value, const wstring& name)
    value.serialize (get_node (name));

(where the get_node function searches for an element of the xml file with the desired name, or creates it itself).

For built-in types, the serialize function template is refined as follows:

template <> void serialize (int& value, const string& name) 
    xml_attribute it = m_node.attribute (name.c_str ());
    if (it) value = it.as_int ();
    else m_node.append_attribute (name.c_str ()).set_value (value);

This function serializes / deserializes depending on the presence of an attribute in the xml file.
The template specialization for pointers to objects that are descendants of the iSerializable interface is also defined:

template <> void serialize (iSerializable*& object, const string& name)

Here the fun begins. The pointer may require any object from the class hierarchy; accordingly, it is required to uniquely determine the class of an object by name and create an object of this particular class.

if (!object) m_factory->get_object (object, m_node.find_child_by_attribute ("name", name.c_str ()).name ());
object->serialize (get_node_by_attr (name));

It is worth noting that here we use the get_node_by_attr function to get a new Node object, which also acts as the get_node function, with the difference that this function does not look for an element by name, but by the value of the “name” attribute, since the name of the element here will be class of the required object.

This is where the m_factory object of the PrototypeFactory class comes into play, which the Node class uses. It passes a pointer to a new object created from the prototype stored in it. If you look at the definition of the class, then the Object structure will be defined there:

struct ObjectBase
    wstring name;
    ObjectBase (const string& _name) : name (_name) {}
    virtual void* get_copy () = 0;
template  struct Object : public ObjectBase
    Object (const string& _name) : ObjectBase (_name) {}
    virtual void* get_copy () override
        return (void*) new Data ();

whose objects are stored in the vector. The contents of this vector are controlled by two functions:

template  void set_object (const string& name)
    if (std::find_if (
        m_prototypes.begin (),
        m_prototypes.end (),
        [name] (ObjectBase* obj) { return obj->name == name; }
        ) == m_prototypes.end ()
        m_prototypes.push_back (new Object (name));
template  void get_object (T*& object, const string& name) const
    auto it = find_if (m_prototypes.begin (), m_prototypes.end (), [name] (ObjectBase* obj) { return obj->name == name; });
    if (it != m_prototypes.end ()) object = (T*) (*it)->get_copy ();
        else throw std::exception ("Prototype wasn't found!");

Thus, we can place objects of any type in PrototypeFactory and receive them, indicating by what name they are stored. In order to control the introduction of objects at the beginning of the factory and their correct removal in the destructor, we had to introduce a global function:

void init_prototypes (Prototypes::PrototypeFactory*);

A function definition will need to be done after all classes are defined. It should contain an input of all the objects necessary for the PrototypeFactory class to work:

void init_prototypes (Prototypes::PrototypeFactory* factory)
    factory->set_object< ConcreteClass > (" ConcreteClass ");

This function will be called in the constructor and will inject objects into the factory.

PrototypeFactory () { init_prototypes (this); }

Thus, we serialize the object by its pointer.

There are also functions in the Node class that allow serializing / deserializing element containers:

Also popular now: