Click here to Skip to main content
15,867,141 members
Articles / Programming Languages / C++

Super Factory

Rate me:
Please Sign up or sign in to vote.
4.78/5 (10 votes)
14 Sep 2011MIT6 min read 40.3K   444   25   18
A single unified interface for creating (almost) any kind of object in C++.

Introduction

With object factory implementations already done to death, you might be wondering what on earth a "Super Factory" is. Simply put, it's an object factory which can create objects of any type and return them through any of their respective base class interfaces.

This is a very useful feature indeed (especially for persistence frameworks). Conventional object factory implementations require the user to either (a.) derive the objects to be created at runtime from a common base class, or (b.) use multiple factories to create heterogeneous objects. A Super Factory exposes a single unified interface to create heterogeneous objects at runtime.

(Please note that the term "Super" here, is used to mean "very generic", or "broad in scope or content"; not a superior method/implementation.)

How to use the code

Using the code is quite easy. All we need do is add one file: SuperFactory.h (see the source code accompanying this article), to our project.

To add Factory support to a class, we need to "register" it in the implementation file (.cpp) for that class. Macros have been included for the most common use cases.

Macro syntax: 

SF_Register_Type[_Base(n)]( <SF_Concrete/SF_Abstract>, <class name>, <base1>, <base2>, ... <base(n)> );

So, for e.g., to register an abstract class A, which doesn't have any base classes, we would use:

SF_Register_Type( SF_Abstract, A );

And to register a concrete class C, which has two base classes A and B, we would use:

SF_Register_Type_Base2( SF_Concrete, C, A, B );

Once the classes are registered, the Factory's Create method can then be used to create objects as required.

A simple example  

Consider the following classes defined in their respective header files:

In A.h

    struct A
    {
        virtual void Print() =0;
        virtual ~A() { }
    };

In B.h

    struct B : public A
    {
        virtual void Print() { cout << "In B::Print" << endl; }
        virtual ~B() { }
    };

In C.h

    struct C : public B
    {
        virtual void Print() { cout << "In C::Print" << endl; }
        virtual ~C() { }
    };

To add Factory support, we register each class in it's respective implementation file:

In A.cpp 

    SF_Register_Type( SF_Abstract, A ); 

In B.cpp

    SF_Register_Type_Base1( SF_Concrete, B, A ); 

In C.cpp

    SF_Register_Type_Base1( SF_Concrete, C, B );

Once that's done, we can use the Factory to create objects as required:

    A *pObj1;
    B *pObj2;
    A *pObj3;

    if( SuperFactory::Create( "B", pObj1 ) )    // Creates a B
        pObj1->Print();                         // In B::Print

    if( SuperFactory::Create( "C", pObj2 ) )    // Creates a C
        pObj2->Print();                         // In C::Print

    if( SuperFactory::Create( "C", pObj3 ) )    // Creates a C!
        pObj3->Print();                         // In C::Print! 

Note that even though classes A and C might be implemented by two different people who know nothing of each other's implementations, the Factory has been indirectly "told" (by the registrations) that A is a base class of C. Hence, the Factory is able to create objects of type C and return them through an interface of type A, like pObj3 in the above example.

Another simple example 

Consider the following classes defined in their respective header files:

In A.h: 

    struct A
    {
        virtual void Print() =0;
        virtual ~A() { }
    };

In B.h: 

    struct B
    {
        virtual void Print() =0;
        virtual ~B() { }
    };

In C.h: 

    struct C : public A, public B
    {
        virtual void Print() { cout << "In C::Print" << endl; }
        virtual ~C() { }
    };

To add Factory support, we register each class in it's respective implementation file:

In A.cpp:

    SF_Register_Type( SF_Abstract, A );

In B.cpp:

    SF_Register_Type( SF_Abstract, B );

In C.cpp:

    SF_Register_Type_Base2( SF_Concrete, C, A, B );

Once that's done, we can use the Factory to create objects as required:

    A *pA;
    B *pB;

    if( SuperFactory::Create( "C", pA ) )   // Creates a C
        pA->Print();                        // In C::Print

    if( SuperFactory::Create( "C", pB ) )   // Creates a C
        pB->Print();                        // In C::Print

Note that C derives from both A and B, so the Factory can create objects of type C and return them through either base interface, A or B.

Working with primitive types 

The Factory can also create primitive types (provided that they're registered).

So, suppose the user wanted the ability to create float and unsigned int objects via the Factory, he would first register them as usual (in one of the implementation files):

    SF_Register_Type( SF_Concrete, float );
    SF_Register_Type( SF_Concrete, unsigned int );

Once that's done, objects of these primitive types can be created as required:

    float *pFloat;
    if( SuperFactory::Create( "float", pFloat ) )
    {
        // Do something with pFloat
    }

    unsigned int *pUint;
    if( SuperFactory::Create( "unsigned int", pUint ) )
    {
        // Do something with pUint
    } 

How does it work? 

It's not required to know how it works in order to use it, so those who are not really interested can skip this section. The working of this Factory is very similar to the one described in the wonderful book "Modern C++ Design".

For each specific product type (concrete/abstract), there exists an instance of a templated factory class to handle it. Each factory can only create objects of the product type it's designed to handle (provided that the product type it handles is not abstract). However, it also has the ability to delegate creation to the factories which handle derived Products.

So when asked to create a product, given a product identifier, the factory first sees if the requested product is the one it's designed to handle. If so, it simply uses the user-supplied product creation function and returns whatever's created to the user. But if the product identifier is not recognized by the factory, it delegates creation to factories which handle derived product types, hoping that the product will be recognized by one of them. This happens recursively until one of the factories in the product hierarchy recognizes the identifier (which means that it knows how to create products of that type), or none of the factories in that product hierarchy recognize the identifier.

If a product identifier is not recognized by any of the factories in that product hierarchy, then:
a.) the product to be created doesn't fall within that hierarchy (i.e., it's a base product, or an unrelated product).
b.) the product to be created was not (or incorrectly) registered with its associated factory.
c.) one of the products in the hierarchy was not (or incorrectly) registered, thereby breaking the "chain" of creation delegation.

If one of the factories in the hierarchy recognizes the product identifier, it creates the product and returns it to its caller, which would be the immediate base product factory. The happens recursively until the product finally reaches the user.

When the user registers a product and its immediate base classes, behind the scenes something tricky happens. The product gets registered straightforwardly with its factory (via the Factory<Product>::Register function). However, as described in the paragraphs above, each product needs information about its derived products (to delegate creation to); not about its base products. Hence there's no "RegisterBase" function; instead there's a "RegisterDerived" function. So, the product is instead registered with the factories of its base classes (via the Factory<Product>::RegisterDerived<T> function). This is how each factory knows about its derived products (even though it appears to the user that he's registering classes and their bases).

Pros/Cons

Pros:

  • Very easy to use and maintain.
  • Exposes a single unified interface to create all objects.
  • Doesn't require RTTI.
  • Doesn't require the objects to be polymorphic.
  • No overhead is added to objects (in terms of time/space).
  • Supports primitive types (float, unsigned int, etc.)
  • Supports (instantiated) templated types. (see Example4 in accompanying code)
  • Can provide Factory services to existing classes without any modifications.

Cons:

  • The implementation provided only has out of the box support for objects which have a public parameterless constructor. However, bypassing the normal registration macros allows you to provide your own functions to create objects, so you can use that to customize object creation. (Otherwise you can always hack the code to support your specific needs (: ).
  • Requires some maintenence if class hierarchies change (but usually, that's not something which happens too frequently).
  • A linear search is employed to find the required object type. The time taken depends on the hierarchical "distance" between the target type and the type to be returned. Normally this shouldn't be a problem, unless you have extremely deep class hierarchies.

Closing 

Please note that in the code examples above, for the purpose of clarity, I've left out deleting the objects allocated by the factory; in real world code you'll want to delete them after use, or use std::auto_ptr (or a smart pointer) to do it automatically for you.

In the accompanying source code, there are two more examples. One shows the Factory working with a diamond inheritance hierarchy, and the other shows the Factory working with templated types.

The source code is released under the MIT license and has been tested on the following compilers:

  • Microsoft Visual C++ 2005/2008/2010
  • GCC 4.4.1 (TDM-2 MinGW32) 
There is much potential for improvement; if you make changes to the code, improve it, or have some better ideas, I would love to know. I can be reached by email at francisxavierjp [at] gmail [dot] com. Comments and suggestions are always welcome!

References 

  1. "Modern C++ Design"
  2. Loki C++ library
  3. Factory method pattern

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
United States United States
Besides loving spending time with family, Francis Xavier likes to watch sci-fi/fantasy/action/drama movies, listen to music, and play video-games. After being exposed to a few video-games, he developed an interest in computer programming. He currently holds a Bachelor's degree in Computer Applications.

Comments and Discussions

 
Questionabout pros & cons Pin
Emilio Garavaglia14-Sep-11 20:48
Emilio Garavaglia14-Sep-11 20:48 
AnswerRe: about pros & cons Pin
Francis Xavier Pulikotil14-Sep-11 21:48
Francis Xavier Pulikotil14-Sep-11 21:48 
GeneralRe: about pros & cons Pin
Emilio Garavaglia15-Sep-11 1:31
Emilio Garavaglia15-Sep-11 1:31 
AnswerRe: about pros & cons Pin
Paul M Watt5-Oct-13 15:22
mentorPaul M Watt5-Oct-13 15:22 
GeneralRe: about pros & cons Pin
Emilio Garavaglia5-Oct-13 21:51
Emilio Garavaglia5-Oct-13 21:51 
GeneralRe: about pros & cons Pin
Paul M Watt6-Oct-13 19:30
mentorPaul M Watt6-Oct-13 19:30 
GeneralRe: about pros & cons Pin
Emilio Garavaglia6-Oct-13 20:50
Emilio Garavaglia6-Oct-13 20:50 
GeneralRe: about pros & cons Pin
Paul M Watt7-Oct-13 13:34
mentorPaul M Watt7-Oct-13 13:34 

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.