Thank you for your input - now I have some more ideas on functionality for the deserialization system :)
Basically,
Quote
if you want to register each instantiated object and then search for the pointer from a global registry for that base class.
sounded like a rather good idea: You need to have a single factory entry point for each (base) type you want to create during deserialization.
My layout now looks like this:
There is a non-template class UntypedFactory, which provides generic (untyped) functionality, and a class template Factory, which does the typed functionality for some type BaseType.
The user, at some point, knows he wants to instantiate an object that is required to be at least of type BaseType (this may be the type of the later object pointer). But actually, the object from the file has a type of ClassName, where ClassName is some string constant.
Additionally, the in-file object has an identifier to be associated with it, for later pointer restoration.
Yet, this means the user also knows that there is at least a specialized Factory for type BaseType. Thus, the user can call a typed method:
BaseType *FactoryObject<BaseType>.createInstance(ClassName, Identifier)
Each Factory includes a list of UntypedFactories, which has one entry for _any_ factory that builds a child class of T.
On instance creation, the UntypedFactory for ClassName is found in this list (or it may be the Factory for BaseType itself - if nothing is found: return NULL).
Then, instance creation calls an abstract method
virtual void* create(ClassName, Identifier) = 0
provided by that UntypedFactory.
This entry point is _not_ implemented in Factory (more on this later),
but does these things:
+ Create (and later return) an object that is of the exact type of the factory - i.e. creates a ClassName
+ Register the _typed_, created object with the _typed_ methods of each parent class Factory of ClassName - registration then continues recursively on further parent.
This requires some elaboration: Have each Factory not only carry a template parameter ContainedType, but also a template parameter BaseFactoryList.
This template parameter is used to provide a list of _typed_ base class factories within the Factory, and allows the registration call to retain type information on recursion.
Essentially, this means a class hierarchy is represented not by a hierarchy of factories, but by a series of same-level, but type-safely connected factory instances.
Once
virtual void* create(ClassName, Identifier) = 0
returns from the UntypedFactory, the list of instances within the typed base factory should now contain a properly typed pointer to an instance of ClassName under the provided identifier -
this typed identifier is now retrieved by lookup, and returned to the client.
Later on, the typed identifier can also be looked up again without object creation - and in any of the BaseClass factories that is compatible to the class that actually was instantiated.
Now for the virtual void* create() method: This one is implemented in a further class template deriving from Factory. Since create() calls the constructor of the class associated with the factory, there may be moments where further information is needed: such as constructor parameters. This can easily be introduced by creating a custom factory class.
On second reading, perhaps a little bit of code makes all of this more clear:
class BaseFactory
{
std::map<String, BaseFactory*> ChildFactories;
virtual void* create(Identifier& identifier) = 0;
}
template<typename ContainedClass, typename BaseFactoryList> Factory :
public BaseFactory
{
....
std::map<Identifier, ContainedClass*) Instances;
BaseFactoryList BaseFactories;
....
void registerInstance(Identifer& identifier, ContainedClass* instance)
{
Instances.insert(pair(identifier, instance));
BaseFactories.registerInstance(identifier, instance);
}
ContainedClass* createInstance(Identifier& identifier, String& ClassName)
{
ActualFactory = Find ClassName in ChildFactories
ChildFactory.create(identifier);
return Instances.find(identifier);
}
ContainedClass* findInstance(Identifier& identifier)
{
return Instances.find(identifier);
}
}
template<ContainedClass, BaseFactoryA, BaseFactoryB>
ExampleBaseFactoryList
{
BaseFactoryA BFA;
BaseFactoryB BFB;
void registerInstance(Identifer& identifier, ContainedClass* instance)
{
BFA.registerInstance(identifier, instance);
BFB.registerInstance(identifier, instance);
}
}
template<typename ContainedClass, typename BaseFactoryList>
DefaultConstructorFactory :
public Factory<ContainedClass, BaseFactoryList>
{
virtual void* create(Identifier& identifier)
{
ContainedClass *instance = new ContainedClass();
registerInstance(identifier, instance);
return instance;
}
}
This allows for completely typesafe reconstruction of entities, for multiple inheritance schemes, and for arbitrary constructors.
Additionally, all factories may be encapsulated into different FactoryContext classes for ease of use - for instance, a GUI loader could have a different list of Factories than a level loader.
The only downsides:
- One extra lookup per instanciated entity (but: RTTI has that one as well if you dynamic_cast)
- Extra memory for lookup arrays. However, for a instance hierarchy of depth N, this is at most a constant factor of N, on some very small (pointer + identifier) overhead (each instance is registered once on each base type)
All over, I'm pretty content with this scheme...
Thanks again,
Cheers,
- Wernaeh
Some call me mathematician, some just call me computer guy. Yet, I prefer the term professional weirdo :)