Generic Class Factory – C++ Introduction Part 1/4

This article is the first in a series of 4 that describes how a generic class can be implemented in C++ and C#.  Each article looks at a different implementation.  The first 2 are in C++ and the last 2 are in C#:

  1. The first C++ generic factory example illustrates the basic idea of how a simple generic class factory works, which is this article.
  2. The second C++ example shows how a generic factory can be used to create any object – even those that don’t share the same base class.
  3. The first C# example shows how an elegant generic factory can be implemented in C# that uses the reflection Activator class and how it can be used to create types directly without having to define any static object creation methods.
  4. The second C# example shows an alternative C# generic factory that uses delegates, much in the same way as the C++ generic factory uses function pointers.  A benchmark is shown that compares the performance of the two C# generic factories as well as objects created with the new operator.

Takeaways:

    • Explains when a class factory should be used over simple object creation.
    • Explains how in C++ a generic class, generic methods, and the use of void pointers as return values for the static creation methods can be utilized to create objects of unrelated types – even types that require varying amounts of arguments.
    • Shows how in C# to use the Type class to store types and the Activator class to create objects with varying amounts of arguments.  Explains why the C# solution is more elegant than the C++ solution in that the same functionality can be accomplished with less code.
    • Shows how to write a generic class and generic methods in C++ and C#.

Introduction

A class factory provides a way to create a specific concrete object based upon a simple key. Why should a factory be used instead of simply creating the object directly?  Most of the time, straight forward and simple objects should be created directly without using a factory – since using a class factory is more complex.  However, there are times when creating an object is a rather involved and complicated process.

A class factory can hold the information required to construct complicated objects.  Using a class factory reduces class dependencies throughout a system.  A class factory removes the dependency on specific concrete classes from the rest of the system which leads to a loosely coupled system.  This means that the code is easier to reuse and test since it has fewer dependencies.  Some examples of when to use a class factory:

  • A specific object needs to be created out of a large number of objects. If a simple key can be used to determine the object to be created, then this is a great time to use a class factory.
  • An object has 15+ dependencies and needs to be created in several different unrelated classes. Sometimes, it is not always easy to access certain assemblies, namespaces, or classes – especially from C++. A class factory could be used here to encapsulate and handle the dependencies.
  • The constructor for an object has a large number of arguments and many of those arguments involve objects that are not easy to create. If many of those arguments can be chosen based upon a simple key, then a class factory could be used to simplify and consolidate object creation.

How A Class Factory Works

  • Register the type along with a key that is used to identify and later retrieve that type so it can be created. In C++, the type is a function pointer to a static object creation method. In C#, this can be implemented by utilizing the Type class. A Hashtable(C++) or Dictionary(C#) is typically used to store the key and type.
  • To create an object with a given key, call the create object method to return a pointer to the newly created object for C++, or a reference to the object in C#. The type of the object is retrieved from the Hashtable via the previous call to the Register method. This method then creates the object and returns a pointer to it.

A  C++ Generic Class To Create Objects of the Same Family

The complete class follows defined in GenericFactory.h:

#include <string>
#include <unordered_map>
#include <iostream>
#include <vector>

using namespace std;

template <class TKey, class TType>
class GenericFactory
{
   // This class implements a generic factory that can be used to create any type with any number of arguments.
   //
   typedef TType* (*CreateObjFn)(vector<void*> &args); // Defines the CreateObjFn function pointer that points to the object creation function.
   typedef tr1::unordered_map<TKey, CreateObjFn> FactoryMap; // Hash table to map the key to the function used to create the object.
public:
   void Register(const TKey &keyName, CreateObjFn pCreateFn)
   {
      // Store the function pointer to create this object in the hash table.
      FactMap[keyName] = pCreateFn;
   }

   TType* CreateObj(const TKey &keyName, vector<void*> &args)
   {
      // This method looks for the name in the hash map.  If it is not found, then an exception is thrown.
      // If it is found, then it creates the specified object and returns a pointer to it.
      //
      FactoryMap::iterator It = FactMap.find(keyName);
      if (It != FactMap.end())
      {
         return It->second(args);
      }
      throw "GenericFactory::CreateObj: key was not found in hashtable.  Did you forget to register it?";
   }
private:
   FactoryMap FactMap;
};

Generics in C++ provide a way to write reusable classes and methods that can work with any type. Generics can be applied to a class factory so that the same set of code can be reused for any type of base class.  The generic class factory above uses 2 class parameters:

  • TKey – type used for key.  Used to keep track of the object to be created. For example, string, int, char, or a unique class could be used here. If a unique class is used, then a hash function and the == operator will need to be written for this class so that the key can be properly stored within the unordered_set and found by the create object method.
  • TType – base class of derived types to be created. Of course for very complicated objects to be created, TType could specify a class that is a concrete class.

There are two typedefs defined:

typedef TType* (*CreateObjFn)(void);

This typedef defines the function pointer CreateObjFn.  It points to the static object creation function. This definition uses the generic class parameter TType as the type to be created and returned.

typedef tr1::unordered_map<TKey, CreateObjFn> FactoryMap;

This typedef defines the hashtable used to map the key to the function used to create the object.  The function pointer is the value stored in the hashtable so that it can be called later to create the object specified by the key.

The Register method is a simple one-line method that stores the function pointer to the static creation method for this type by keyName.  This static creation method must have been previously defined by the user.

void Register(const TKey &keyName, CreateObjFn pCreateFn)
{
   FactMap[keyName] = pCreateFn;
}

The CreateObj method should be called to create an object for a given key that has been registered:

TType* CreateObj(const TKey &keyName)
{
   FactoryMap::iterator it = FactMap.find(keyName);
   if (it != FactMap.end())
      return it->second();
   throw "GenericFactory::CreateObj: key was not found in hashtable. Did you forget to register it?";
}

This method looks for the key in the hash table. If it is not found, then most likely a call to the Register method was overlooked.  An exception is thrown in this case.  If the key is found, then the static creation method pointed to by the CreateObjFn pointer is called.

The following code shows how to use the generic factory:

GenericFactory<string, Animal> GenFactAnimal; // Create an animal class factory that uses a string keys.
GenFactAnimal.Register("dog1", &Dog::CreateObject); // Register "dog1" to create a dog object.
// Prepare the arguments needed to create a specific dog object named Rex who is 1 years old.
vector<void*> Args;
Args.push_back("Rex");
int YearsOld = 1;
Args.push_back(&YearsOld);
// Create a dog animal object from the factory.
unique_ptr<Animal> pDog(GenFactAnimal.CreateObj("dog1", Args));

A unique_ptr smart pointer is used to support the Resource Allocation Is Initialization (RAII) pattern.  The benefits of RAII are automatic deletion when the object goes out of scope, encapsulation, and exception safety.  Using a smart pointer means that you will never have to worry about forgetting to clean up an object or leaking memory ever again.

The code for this class may be downloaded by following this link.

Advertisements

About Bob Bryan

Software developer for over 20 years. Interested in efficient software methodology, user requirements, design, implementation, and testing. Experienced with C#, WPF, C++ , VB, Sql Server, stored procedures, and office tools. MCSD.
This entry was posted in software development and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s