Singleton factories, shared_ptr, and serialization, oh my!
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
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).
This pretty much replicates what the serialization system does when serializing class marked with BOOST_CLASS_EXPORT
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; } };
The above is supported exactly as written by the serialization library
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; } };
The above is also supported exactly as written by the serialization library. Its in the documentation. The explanation particular to shared_ptr<T> is in the case study - shared_ptr;
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.
Hmmm - I far as I can see it was redundant in the first place.
If I try to combine all of this in the BaseHandle class, I run into difficulties:
... The above is what you want. In fact I would make it even simpler in this particular case: class Thing { shared_ptr<Base> my_base; template <class Archive> void serialize(Archive& ar, unsigned int) const { ar & my_base; } }; BOOST_SHARED_POINTER_EXPORT(DerivedProductionA) BOOST_SHARED_POINTER_EXPORT(DerivedProductionB) ... // other classes derived from Base I think that would be all that is necessary. Robert Ramey
On Saturday, February 19, 2005 at 13:58:07 (-0600) Bill Lear writes:
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.
Ok, this took so long to show up that I solved the problem, although
someone else may have a better solution.
Here is a (working) sketch of my solution, below my sig, if anyone is
interested.
Bill
#include <string>
#include <iostream>
#include <fstream>
#include <map>
using namespace std;
#include
participants (2)
-
Bill Lear
-
Robert Ramey