Discussing the nuts and bolts of software development

Wednesday, May 14, 2008

 

Java programmer living in a C++ world

Being a long time Java programmer, I've become familiar and fond of many of the features provided by the language. Reflection and runtime type identification add an extra level of power to any programming language. When I returned to C++ development, I found it necessary to fill the void I had grow accustomed to in the Java world.

Inversion of control in C++

Inversion of control is a powerful design pattern (and/or philosophy) that allows for loosely coupled and highly reusable objects. The main principle is that an owner object is responsible for supplying all the needed information and resources to any object contained within. This includes configuration, initialization, logging, thread pools, databases and any other conceivable resource.

When dealing with inversion of control, it's often desirable to test if a particular object implements a specific interface. An object may implement the Configurable interface, but not the Loggable interface. This is easy enough to do in Java using the "instanceof" keyword or via reflection. In C++, you must do things a bit different:


class Configuration;

class Configurable
{
public:
virtual void configure(const Configuration &configuration) = 0;
template<class>
static bool tryConfigure(T *obj,
const Configuration &configuration)
{
Configurable *configurable = dynamic_cast<Configurable>(obj);
if (configurable)
{
configurable->configure(configuration);
return true;
}
return false;
}
};


In this case, a owner object can attempt to configure a child object simply by calling the static method tryConfigure. If it the supplied object is of the wrong type, it will simply return false.


Configuration config;
Configurable::tryConfigure( &ownedObjectA, config );


The same can be applied for initializing and de-initializing objects. Note that the static testing method is templated so the object type is not lost when passed in.


class Initializable
{
public:
virtual bool initialize(void) = 0;
virtual bool deinitialize(void) = 0;

template<class>
static bool tryInit(T *obj)
{
Initializable *init = dynamic_cast<Initializable>(obj);
if (init)
{
return init->initialize();
}
return false;
}

template<class>
static bool tryDeinit(T *obj)
{
Initializable *init = dynamic_cast<Initializable>(obj);
if (init)
{
return init->deinitialize();
}
return false;
}
};

Singletons in C++

Singleton is the most basic of all patterns, but extremely valuable. Care must be taken not to overuse singletons, but in the right place, they are a design gem. In C++ there is a simple template way to reduce the coding overhead of returning a static instance, and also unify the method names to get the instance.

template <class> class Singleton
{
public:
// Virtual destructor
virtual ~Singleton() {}

// Get instance as pointer
inline static Target *ptr(void) { return &(ref()); }

// Get instance as reference
inline static Target &ref(void) {
static Target theInstance;
return theInstance;
}
protected:
Singleton(void) {} // Default constructor
};

To inherit a singleton object, one extra step must be taken so that the inherited constructor is called. That is to add the templated version of singleton as a friend to the class.


class MySingletonClass : public Singleton<MySingletonClass>
{
friend class Singleton<MySingletonClass>;
};

NOTE: Some compiler optimizations of a static inline method might cause undesirable effects, such as multiple instances of a singleton. You may need to twiddle optimization flags.


Factories in C++

Factories are responsible for creating objects of a specific interface type. For example, you could have a LoggerFactory that is able to create a PlainTextLogger, an XMLLogger and a SocketLogger. Unlike Java, in C++ it is difficult to dynamically create an instance of a class based on its name alone. By combining a Prototype pattern and a C macro, it is possible to register a class by name, and instantiate it later by name. This is somewhat analogous of querying an interface in Microsoft COM.


#include <string>
#include <map>

class Prototype
{
public:
virtual bool createInstance(void **instance) = 0;
};

template<class>
class PrototypeTemplate : public Prototype
{
public:
virtual bool createInstance(void **instance) {
if (instance)
{
*instance = new T();
return true;
}
return false;
}
};

#define xstr(s) #s
#define PROTOTYPE(x) xstr(x), new PrototypeTemplate<x>()


template<class>
class Factory
{
public:

bool queryPrototype(const std::string &name, T **instance)
{
if (name.empty() || (!instance))
return false;

if (m_registeredPrototypes.find(name) != m_registeredPrototypes.end())
{
return m_registeredPrototypes[name]->createInstance(
(void **) instance);
}
return false;
}

virtual ~Factory(void)
{
// Release the prototypes
for (std::map<std::string,>::iterator it =
m_registeredPrototypes.begin();
it != m_registeredPrototypes.end(); it++)
delete it->second;
}
protected:

/* Protected by default. Can re-expose as public if you want
outside code able to register new prototypes */
void registerPrototype(const std::string &name, Prototype *prototype)
{
m_registeredPrototypes[name] = prototype;
}

std::map<std::string,> m_registeredPrototypes;
};

A typical implementation may look like this:


#include "Factory.h"
#include "Singleton.h"

#include "Logger.h"
#include "PlainTextLogger.h"
#include "XMLLogger.h"
#include "SocketLogger.h"

namespace Protected {
// This namespace is really just an attempt to hide the base type, since it must
// first be templated as a factory, and then as a singleton.

class LoggerFactory : public Factory<Logger>
{
public:
LoggerFactory(void)
{
registerPrototype( PROTOTYPE( PlainTextLogger ) );
registerPrototype( PROTOTYPE( XMLLogger ) );
registerPrototype( PROTOTYPE( SocketLogger ) );
}
};
}


class LoggerFactory : public Protected::LoggerFactory,
public Singleton<LoggerFactory>
{
friend class Singleton<LoggerFactory>
};

We can now create a logger dynamically by specifying the name of the class as a string. In this manner, the logger could be determined at runtime from a configuration item. It would also be possible to register prototypes at runtime via DLL or shared objects.


Configuration config;
Logger *myLogger;
std::string loggerTypeName;

if( !config.getValue("logger", loggerTypeName ) ||
loggerTypeName.empty() ||
!LoggerFactory::ref().queryPrototype( loggerTypeName, &myLogger ) )
{
// Not found, default to known existing logger
loggerTypeName = "PlainTextLogger";
LoggerFactory::ref().queryPrototype( loggerTypeName, &myLogger );
}
Configurable::tryConfigure( myLogger, config.getSubConfiguration( loggerTypeName ) );
Initializable::tryInit( myLogger );


Comments:
I have read your blog its very attractive and impressive. I like it your blog.

Java Training in Chennai Core Java Training in Chennai Core Java Training in Chennai

Java Online Training Java Online Training Core Java 8 Training in Chennai Core java 8 online training JavaEE Training in Chennai Java EE Training in Chennai
 
Java Online Training Java Online Training Java Online Training Java Online Training Java Online Training Java Online Training

Hibernate Online Training Hibernate Online Training Spring Online Training Spring Online Training Spring Batch Training Online Spring Batch Training Online
 
Java Training Institutes Java Training Institutes Java EE Training in Chennai Java EE Training in Chennai Java Spring Hibernate Training Institutes in Chennai J2EE Training Institutes in Chennai J2EE Training Institutes in Chennai Core Java Training Institutes in Chennai Core Java Training Institutes in Chennai

 
Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?