Introduction
This is the article about possible implementation of class factory, which uses dynamic subscription. It allows creation of objects based on custom criterion, useful, for example, in parsing of XML or other type of data, or for creating objects loaded from database.
Background
Has it happened to you that you had to write a huge switch
command doing nothing but creating new objects? And include all the headers for all the objects into one CPP file? And then, when you made a small change in one of the headers, it took ages to compile?
Something similar to this:
CBase* pBase(NULL);
switch (type_variable)
{
case obj1: pBase = new CBaseDerivate1();break;
case obj2: pBase = new CBaseDerivate2();break;
...
case objN: pBase = new CBaseDerivateN();break;
}
or worse:
CBase* pBase(NULL);
if (!strcmp(string_for_type, "Type1")) pBase = new CBaseDerivate1();
else if (!strcmp(string_for_type, "Type2")) pBase = new CBaseDerivate2();
...
else if (!strcmp(string_for_type, "TypeN")) pBase = new CBaseDerivateN();
P.S.: This could be even worse when complex conditions are used.
Tackling the Problem
When I was implementing an XML parser and loading stored objects from DB, I got a lot of similar constructions. Well, I got tired of it quite soon. After becoming more experienced, I started to think about it. And recently, when I needed to use similar code again, I found out the following approach...
What I actually want is a 'class factory', something that would create classes for me.
CBase* pBase = CClassFactory::CreateInstance(type_variable);
I could hide object construction into a separate class factory. In ATL, class factory uses a static array of available classes with pointer into CreateInstance
function. New entries are added by the wizard. But the problem with headers and compilation (still lot of the includes would go into CPP) remains here.
Then, I came up with an idea - what about static array/map? But populated dynamically? And all classes would 'register' their creator function themselves. Would it be possible? How?
The idea of static array/map populated dynamically sounded very strange at the beginning, but what about declaring std::map
/vector
as static? And populate it by some clever mechanism later... Yep, we can do it.
Now, for the self registration bit. It took me some moments to come up with the solution, but it was simple (once found). We can sneak some code into initialization of static dummy variable... Like:
int CMyClass::iDummy =
ClassFactory::RegisterCreatorFunction(key, CMyClass::Creator)
where function RegisterCreatorFunction
will return any integer and add creator function pointer (must be static too) into array/map.
OK, you might say... This seems quite easy to do.
So, now we have:
static std::map<key, pointer_to_create_function> mapOfCreators;
class CBaseDerivate1: public CBase
{
static CBase* create_function();
static key DummyVariable;
}
in header and
key CBaseDerivate1::DummyVariable = RegisterCreatorFunction(key,
CBaseDerivate1::create_function)
in CPP file.
If you try it, there is a 50% chance that it will work. Yes, there is a catch: it might happen that static dummy will be initialized before the initialization of static array/map. And this whole code will throw an unhandled exception. But there is a cure for it - we won't access static array directly but by static get
function, so the array/map will be created at the first access...
So instead of:
static std::map<key, pointer_to_create_function> mapOfCreators;
we need a 'get
' function:
static std::map<key, pointer_to_create_function> * get_mapOfCreators()
{
static std::map<key, pointer_to_create_function> _mapOfCreators;
return &_mapOfCreators;
}
Solution
And when we encapsulate everything into class, we can get something like:
template <typename _Key, typename _Base,
typename _Predicator = std::less<_Key> >
class CClassFactory
{
public:
CClassFactory() {};
~CClassFactory() {};
typedef _Base* (*CreatorFunction) (void);
typedef std::map<_Key, CreatorFunction, _Predicator> _mapFactory;
static _Key RegisterCreatorFunction(_Key idKey,
CreatorFunction classCreator)
{
get_mapFactory()->insert(std::pair<_Key,
CreatorFunction>(idKey, classCreator));
return idKey;
}
static _Base* CreateInstance(_Key idKey)
{
_mapFactory::iterator it = get_mapFactory()->find(idKey);
if (it != get_mapFactory()->end())
{
if (it->second)
{
return it->second();
}
}
return NULL;
}
protected:
static _mapFactory * get_mapFactory()
{
static _mapFactory m_sMapFactory;
return &m_sMapFactory;
}
};
So, Let's Sum It Up
Now, everything is prepared to be used. To do so, it is necessary:
- Decide, what to use as a key (e.g., integer/string/GUID/pair of values) and define the corresponding predicate functor.
- In class definition, add
static
"creator
" function and dummy variable:
static CSampleBase* SampleCreatorFunction() {return new CSampleOne;}
static int iDummyNr;
- To initialize dummy variable, call the code to register
creator
function:
int CSampleOne::iDummyNr = CClassFactory<int,
CSampleBase >::RegisterCreatorFunction(1,
CSampleOne::SampleCreatorFunction);
- And to create an object, just call:
CSampleBase * pBase = CClassFactory<int,
CSampleBase >::CreateInstance(1);
Known Constraints
This class will work perfectly if you don't start to play with multiple threads. It is well known(?) that MS <map>
itself doesn't play nicely in multithreading environment, so that would be the first place to watch... My solution was to protect all class calls by critical section, but it wasn't a generic solution so I won't include it here.
Also, the way it is presented here allows only one instance of class factory per combination of Key/Base/Predicator. Which is OK for most instances, but there might be cases when it isn't - then a new template parameter will help.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.