简体   繁体   中英

C++ templated factory constructor/de-serialization

I was looking at the boost serialization library, and the intrusive way to provide support for serialization is to define a member function with signature (simplifying):

class ToBeSerialized {
public:
    //Define this to support serialization
    //Notice not virtual function!
    template<class Archive>
    void serialize(Archive & ar)
    {.....}
};

Moreover, one way to support serilization of derived class trough base pointers is to use a macro of the type:

//No mention to the base class(es) from which Derived_class inherits
BOOST_CLASS_EXPORT_GUID(Derived_class, "derived_class")

where Derived_class is some class which is inheriting from a base class, say Base_class. Thanks to this macro, it is possible to serialize classes of type Derived_class through pointers to Base_class correctly.

The question is: I am used in C++ to write abstract factories implemented through a map from std::string to (pointer to) functions which return objects of the desired type (and eveything is fine thanks to covariant types).

Hover I fail to see how I could use the above non-virtual serialize template member function to properly de-serialize (ie construct) an object without knowing its type (but assuming that the type information has been stored by the serializer, say in a string).

What I would like to do (keeping the same nomenclature as above) is something like the following:

XmlArchive xmlArchive; //A type or archive
xmlArchive.open("C:/ser.txt"); //Contains type information for the serialized class
Base_class* basePtr = Factory<Base_class>::create("derived_class",xmlArchive);

with the function on the righ-hand side creating an object on the heap of type Derived_class (via default constructor, this is the part I know how to solve) and calling the serialize function of xmlArchive (here I am stuck!), ie do something like:

Base_class* Factory<Base_class>::create("derived_class",xmlArchive)
{
    Base_class* basePtr = new Base_class; //OK, doable, usual map string to pointer to function
    static_cast<Derived_class*>( basePtr )->serialize( xmlArchive ); //De-serialization, how?????
    return basePtr;
}

I am sure this can be done (boost serialize does it but its code is impenetrable! :P), but I fail to figure out how. The key problem is that the serialize function is a template function. So I cannot have a pointer to a generic templated function. As the point in writing the templated serialize function is to make the code generic (ie not having to re-write the serialize function for different Archivers), it does not make sense then to have to register all the derived classes for all possible archive types, like:

MY_CLASS_REGISTER(Derived_class, XmlArchive);
MY_CLASS_REGISTER(Derived_class, TxtArchive);
...

In fact in my code I relies on overloading to get the correct behaviour:

void serialize( XmlArchive& archive, Derived_class& derived );
void serialize( TxtArchive& archive, Derived_class& derived );
...

The key point to keep in mind is that the archive type is always known, ie I am never using runtime polymorphism for the archive class...(again I am using overloading on the archive type).

Any suggestion to help me out?

Thank you very much in advance!

Cheers

If your archive type is always known, why bother parameterizing your serialize function on it? There's an argument to be made for code reuse, yes, but if your Archive class ever changes its definition or gets replaced, you will likely need to refactor some of your serialization code anyway.

If you stick with:

class ToBeSerialized : public Base_class {
public:
    void serialize(Archive & ar)
    {.....}
};

You can then take a pointer to your serialize function, and bind it to your factory.

You will also need to bind separate create functions for each class, so that it can instantiate the right type when it's asked for it. Something like:

template <typename T> Base_class* Factory::create(Archive& xmlArchive) {
    T* derivedPtr = new T;
    derivedPtr->serialize( xmlArchive );
    return derivedPtr;
}

The factory will then need a generic create method that calls out to the correct static parameterized create<T> :

Base_class* Factory::create(const char* typeString, Archive& xmlArchive) {
    // Pseudocode.
    return m_map.find(typeString)->callCreate(xmlArchive);
}

All you need is to store some sort of identifier before storing the information from the derived type. Then upon reading you use that identifier, which you've read first, to direct you to a factory that can then interpret the next block of information correctly and generate your derived type. This is probably what boost::serialization does at a very basic level. Maybe something like so:


ar >> type;
Base_class* basePtr = Factory<Base_class>::create(type,xmlArchive);

Then you have a map of objects that look something like so:

struct reader_base { virtual void load(xmlArchive, base_ptr) = 0; } template < typename T > struct reader : reader_base { virtual void load(xmlArchive, base_ptr) { static_cast<T*>(base_ptr)->serialize(xmlArchive); } };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM