HOME PROJECTS SOFTWARE HARDWARE OTHER FACTS LINKS

You can find articles about class factories all over the internet and in C++ programming books. However I only found one that broke the traditional way to create class instances that did not require an ugly cast from a void*. Unfortunately I cannot find any more a reference to this article. The solution was elegant and involves the use of templates. The basic idea is simple and is based around the fact that if you want to create an instance of a class, in most cases that class will be a constructed from a base class and the instance is generally casted to the base class as you don't know the object type at runtime. Therefore the idea is to create a class factory for each base class and return a pointer to the base class when generating an instance of a given class. The only detail not included in this article was the use of parameters in the class constructor but was mention at the end of the document as an exercise to do! So I tried to do that exercise as I needed the option when creating windows at runtime and ended up with my own class_factory<>. You can download its source here which is taken from my library ocfc.

To understand how this all work, let's look at a classic example with the animal base class.

class animal { private: string __m_sType; int __m_nAge; public: virtual ~animal() {} inline const string& type() const { return __m_sType; } inline int age() const { return __m_nAge; } protected: inline animal(const string& __sType) : __m_sType(__sType), __m_nAge(0) {} inline animal(const string& __sType, int __nAge) : __m_sType(__sType), __m_nAge(__nAge) {} };

We will add some complexity by introducing an intermediate class called pet as follows:

class pet : public animal { public: virtual ~pet() {} protected: inline pet(const string& __sType) : animal(__sType) {} inline pet(const string& __sType, int __nAge) : animal(__sType, __nAge) {} };

Now let's define three classes of type animal

class dog : public pet { public: inline dog() : pet("mammal") {} inline dog(int __nAge) : pet("mammal", __nAge) {} }; class cat : public pet { public: inline cat() : pet("mammal") {} inline cat(int __nAge) : pet("mammal", __nAge) {} }; class eagle : public animal { public: inline eagle() : animal("bird") {} inline eagle(int __nAge) : pet("bird", __nAge) {} };

In order to create an instance for any of those classes, we first need to declare a virtual factory for class animal as the base class for all our factories even if you cannot instantiate an animal by itself (protected constructor as you can see).

OCFC_DECLARE_CLASS_GUID(animal) OCFC_START_DECLARE_VIRTUAL_BASE_CLASS_FACTORY(animal) CLASS_DEFAULT_CONSTRUCTOR CLASS_CONSTRUCTOR(int) OCFC_END_DECLARE_VIRTUAL_BASE_CLASS_FACTORY

The first macro defines a GUID (unique global ID) for the class that will be used as a hash key. Then we declare the list of possible constructors. As we want to be able to create instances of dog or cat of type pet as well we need to declare another virtual class factory for the latter class like this:

OCFC_DECLARE_CLASS_GUID(pet) OCFC_DECLARE_VIRTUAL_CLASS_FACTORY(pet, animal)

Here we don't need to list the constructors as this is only done for the base class. We simply declare pet factory as being based on animal factory. Next we can declare the factories for our three top classes.

OCFC_DECLARE_CLASS_GUID(dog) DECLARE_CLASS_FACTORY(dog, animal) OCFC_DECLARE_CLASS_GUID(cat) DECLARE_CLASS_FACTORY(cat, animal) OCFC_DECLARE_CLASS_GUID(eagle) DECLARE_CLASS_FACTORY(eagle, animal)

Notice that the base class factory is still animal and not pet. That's because at the end of the day the base class, equivalent to the void* in other class factory libraries, is animal. Finally we can implement those class factories. Don't worry this is as simple as the following:

IMPLEMENT_CLASS_GUID(animal) IMPLEMENT_CLASS_GUID(pet) IMPLEMENT_CLASS_GUID(dog) IMPLEMENT_CLASS_FACTORY(dog) IMPLEMENT_CLASS_GUID(cat) IMPLEMENT_CLASS_FACTORY(cat) IMPLEMENT_CLASS_GUID(eagle) IMPLEMENT_CLASS_FACTORY(eagle)

Having done all that now we can generate instances of our classes. For example:

animal* __pa = new_instance<animal>(CLASS_GUID(dog)); pet* __pp = new_instance<pet>(CLASS_GUID(dog)); dog* __pd = new_instance<dog>(CLASS_GUID(dog)); // next call will return a NULL pointer pet* __pError = new_instance<pet>(CLASS_GUID(eagle), false); assert(__pError == NULL); // or next call will throw an bad typeid exception __pError = new_instance<pet>(CLASS_GUID(eagle)); // however you can have the following __pa = new_instance<animal>(CLASS_GUID(eagle));

Each pointer has been created using the new operator. Therefore you can delete the pointers using the normal delete operator. Ok, now let's try to create an instance of a dog which is 8 years old:

int __nAge = 8; animal* __pa = new_instance<animal>(CLASS_GUID(dog), __nAge); // to use a constant instead of a variable of the right type: typedef class_factory_constructor_parameter<int>::type animal_constructor_parameter; __pa = new_instance<animal>(CLASS_GUID(dog), animal_constructor_parameter(8));

That's it. My library ocfc makes use extensively of this feature. In particular when loading resources into memory.

Valid XHTML 1.0 Strict