Boost logo

Boost Users :

From: Bill Lear (rael_at_[hidden])
Date: 2005-02-19 14:58:07


I have a long question on how to combine a Singleton factory, serialization,
shared pointers and an object hierarchy half of which will be created
by a factory, and half of which will not.

I think solving this problem in an elegant way might be very useful
for others, so if someone with more experience with these things could
take the time to plough through this, we might get something that is
very valuable to the rest of the community.

I have a base class (Base) and several derived classes that fall into
two categories: production classes that are created by a singleton
factory, and test derived classes that are created "by hand" in test
code:

    Base
    DerivedProductionA
    DerivedProductionB
    DerivedTestA
    DerivedTestB

A Singleton factory is used to create "production" Base objects by
examining a string key and available data on the file system. If
certain conditions obtain, DerivedProductionA is created, otherwise
DerivedProductionB is created, and the key used to create the object
is used to set a property in the object so it "knows" its creation
key. The created object is stored in a hash map by key and a
reference (currently, a raw pointer, but I'm contemplating using
shared_ptr) to the object is returned.

The Base object is used by other objects that need to be serialized:

    class Thing {
        Base* my_base;

        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            // TBD
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            // TBD
        }
    };

Currently, the save/load methods inspect the Base object's creation
key. If the key is blank, the object is serialized directly (a test
class). If the key is not blank, the creation key is serialized
(thus, a "production" class). Before this is done, a boolean flag is
serialized indicating that a creation key was used to store the object
or the object itself was stored. This is it, in rough form:

        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            bool flag = key.size() != 0;

            ar << flag;

            if (flag) {
                ar << my_base->key();
            } else {
                ar << my_base;
            }
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            bool flag;

            ar >> flag;

            if (flag) {
                string key;
                ar >> key;
                my_base = Factory::create(key);
            } else {
                ar >> my_base;
            }
        }

Now, this works just fine, but it is a bit inelegant (a friend calls
this uninheritance).

I would prefer to be able to encapsulate the logic of serialization
in a separate class, something like this:

    class BaseHandle {
        Base* my_base;

        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            // As above.
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            // As above.
        }

    };

Then, the Thing class, and any other class that would want
to serialize a Base object would be straightforward:

    class Thing {
        Base* my_base;

        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            ar << my_base;
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            ar >> my_base;
        }
    };

My question is: Has anyone else run across this type of pattern? I
would prefer to use a shared pointer to the Base class throughout and
to hide the constructors of all Base (and its derived classes), to
force allocation of these objects through special static alloc methods
that create shared_ptr-wrapped objects.

My problem is I don't quite know how to combine all of these ideas.

For example, using shared_ptr, class Thing would be quite simple:

      typedef shared_ptr<Base> BasePtr;

      class Thing {
        BasePtr my_base;

        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            ar << my_base;
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            ar >> my_base;
        }
    };

However, now I've broken the previous work: I'd have to go back and
put all of the serialization logic for factory construction back
in the Thing class itself.

If I try to combine all of this in the BaseHandle class, I run into
difficulties:

    typedef shared_ptr<Base> BasePtr;

    class BaseHandle {
        BasePtr my_base;

        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            bool flag = key.size() != 0;

            ar << flag;

            // Ok, now what? Do I just proceed as before and
            // pick up the pieces in the load method??
            if (flag) {
                ar << my_base->key();
            } else {
                ar << my_base;
            }
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            bool flag;

            ar >> flag;

            // Ok, now I know how my BasePtr was created: either
            // through the factory, or through the static alloc() method.
            // But, is the following appropriate?
            if (flag) {
                string key;
                ar >> key;
                my_base = Factory::create(key);
            } else {
                ar >> my_base;
            }
        }

    };

I tried sketching this out with BaseHandle inheriting from shared_ptr
(don't even know if that's possible or at all advisable), which seemed
to me to be the most elegant solution:

    typedef shared_ptr<Base> BasePtr;

    class BaseHandle : BasePtr {
        template <class Archive>
        void save(Archive& ar, unsigned int) const {
            bool flag = key.size() != 0;

            ar << flag;

            // Is this reasonable?
            Base* my_base = get();

            if (flag) {
                ar << my_base->get();
            } else {
                ar << my_base;
            }
        }

        template <class Archive>
        void load(Archive& ar, unsigned int) {
            bool flag;

            ar >> flag;

            // Oy, now what??

            Base* my_base;

            if (flag) {
                string key;
                ar >> key;
                my_base = Factory::create(key);
            } else {
                ar >> my_base;
            }

            // OK, I've got Base* now, fully built.
            // how do I make "this" (shared_ptr) wrap
            // this??

            // Do I do this? :
            *this = BasePtr(my_base);

            // or this??:
            reset(my_base);
        }
    };

If someone could help out here, I'd really appreciate it.

Thanks.

Bill


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net