Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / Objective C

Dynamic Class Factory

Rate me:
Please Sign up or sign in to vote.
4.38/5 (19 votes)
18 Nov 2018CPOL3 min read 90.7K   1.4K   29   15
An article about class factory with dynamic subscription / auto registration

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:

C++
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:

C++
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.

C++
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:

C++
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:

C++
static std::map<key, pointer_to_create_function> mapOfCreators;

class CBaseDerivate1: public CBase
{
    static CBase* create_function();
    static key DummyVariable;
}

in header and

C++
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:

C++
static std::map<key, pointer_to_create_function> mapOfCreators;

we need a 'get' function:

C++
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:

C++
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;

    // called at the beginning of execution to register creation functions
    // used later to create class instances
    static _Key RegisterCreatorFunction(_Key idKey, 
                              CreatorFunction classCreator)
    {
        get_mapFactory()->insert(std::pair<_Key, 
             CreatorFunction>(idKey, classCreator));
        return idKey;
    }

    // tries to create instance based on the key
    // using creator function (if provided)
    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:
    // map where the construction info is stored
    // to prevent inserting into map before initialisation takes place
    // place it into static function as static member, 
    // so it will be initialised only once - at first call
    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:
    C++
    static CSampleBase* SampleCreatorFunction() {return new CSampleOne;}
    C++
    static int iDummyNr;
  • To initialize dummy variable, call the code to register creator function:
    C++
    int CSampleOne::iDummyNr = CClassFactory<int, 
        CSampleBase >::RegisterCreatorFunction(1, 
        CSampleOne::SampleCreatorFunction);
  • And to create an object, just call:
    C++
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralTo get the code to compile on more recent versions of g++ Pin
rm_2517-Apr-09 4:49
rm_2517-Apr-09 4:49 
Add 'typename' before the declaration of 'it' in 'CreateInstance':

static _Base* CreateInstance(_Key idKey)
{
	typename _mapFactory::iterator it = get_mapFactory()->find(idKey);

	if (it != get_mapFactory()->end())
	{
		if (it->second)
		{
                   ...


This is way it is needed:
http://www.parashift.com/c++-faq-lite/templates.html#faq-35.18
Questionregarding clean-up Pin
jayeshskul18-Nov-06 20:09
jayeshskul18-Nov-06 20:09 
AnswerRe: regarding clean-up Pin
mimicry19-Nov-06 12:04
mimicry19-Nov-06 12:04 
QuestionRe: regarding clean-up Pin
jayeshskul23-Nov-06 6:16
jayeshskul23-Nov-06 6:16 
GeneralGood Job!! Pin
Member 44456820-Oct-05 19:43
Member 44456820-Oct-05 19:43 
GeneralBeware of using in static libraries Pin
7-ZARK-726-Jul-04 1:05
7-ZARK-726-Jul-04 1:05 
GeneralRe: Beware of using in static libraries Pin
mimicry26-Jul-04 2:39
mimicry26-Jul-04 2:39 
GeneralRe: Beware of using in static libraries Pin
7-ZARK-728-Jul-04 21:22
7-ZARK-728-Jul-04 21:22 
GeneralRe: Beware of using in static libraries Pin
shaochen198029-Jul-04 3:49
shaochen198029-Jul-04 3:49 
GeneralRe: Beware of using in static libraries Pin
7-ZARK-72-Aug-04 1:21
7-ZARK-72-Aug-04 1:21 
GeneralRe: Beware of using in static libraries Pin
jhuang_sugarcane22-Nov-04 22:59
jhuang_sugarcane22-Nov-04 22:59 
GeneralAnd very similar to one of my examples Pin
Patje25-Jul-04 21:03
Patje25-Jul-04 21:03 
GeneralThis seems similar to the &quot;Abstract Factory&quot; design pattern from GoF. Pin
Don Clugston25-Jul-04 13:58
Don Clugston25-Jul-04 13:58 
GeneralRe: This seems similar to the &quot;Abstract Factory&quot; design pattern from GoF. Pin
mimicry25-Jul-04 23:41
mimicry25-Jul-04 23:41 
GeneralRe: This seems similar to the &quot;Abstract Factory&quot; design pattern from GoF. Pin
John M. Drescher26-Jul-04 11:48
John M. Drescher26-Jul-04 11:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.