Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic Class Factory

0.00/5 (No votes)
18 Nov 2018 5  
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:

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;

    // 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:
    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.

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