Introduction
Object creation is an important step in any Object Oriented Application. An application can use the services of a class only after creating an object of it. In most applications, creating an object of a class is very straight forward, that is, by declaring the instance of the class. For example, to create an object MyObj of class MyClass
, the C++ syntax is MyClass
MyObj;. But, in a complex Object Oriented Application, the task of creating the objects is usually assigned to other classes. The book, Design Patterns : Elements of Reusable Object-Oriented Software by Erich Gamma et al. ( Addison-Wesley, 1995 ) explores different approaches that can be used for object creation under the classification, Creational Patterns.
This article discusses a Creational Pattern called Singleton and explains different approaches for implementing Singleton pattern using a Visual C++ example.
What is a Singleton Class?
Sometimes, you may want to have only one object for a given class and this object should be easily accessible. For example, an Application should have only one Application object and the Job schedulers should share a single Job Queue object. A global variable may provide easy access, but using a global variable will not stop the user from creating more than one object and moreover it is discouraged in Object Oriented Programming. In the above example, the Application class should not allow the user to create more than one object of it and the Job Queue class should ensure that a single Job Queue object is shared by all the Job schedulers. Application and Job Queue classes are called Singleton classes. So, what is a Singleton class ? A class that assures a maximum of ONE object of its type at a given time and provides a global access point to this object is a Singleton class.
An Example
This article uses "Message Handlers" as an example to explain the concept of a Singleton class. An application interacts with the user through messages. This includes errors, warnings and information messages. The application can use a single message handler object to format or log these messages before presenting it to the user. Hence, the message handler class can be a Singleton class.
The approaches that are presented in this article includes
Singletons using static variables
Listing 1 contains a simple message handler class. The constructor and destructor are declared as protected members to avoid direct object creation or destruction. The static method GetMsgHndlr
returns a pointer to the static member m_MsgHndlr.
Listing 1a shows a variation of message handler class, and it uses a function static member instead of a class static member. The object is created only when GetMsgHndlr
is called for the first time.
Advantage
- Ensures only one message handler object is present all the time and eliminates the need for a global variable.
- Message handler using function static member ( Listing 1a ) creates the object, only when GetMsgHndlr is called for the first time. So the object is created only if it is needed.
Disadvantage
Class of the object CMsgHndlr
is hard coded and the application cannot use a different message handler, without changing the class.
Singletons on heap
Listing 2 contains a message handler class that creates object on the heap. A key is passed as a parameter to GetMsgHndlr
for creating the required message handler object. Now, the message handler class not only ensures the uniqueness of the message handler object, but it is also responsible for creating the required message handler object. An object is created when GetMsgHndlr is called for the first time and the subsequent call returns the created object.
Advantage
Applications can select the required message handler by passing the appropriate key. Only message handler class needs to be changed to include a new message handler.
Disadvantage
CMsgHndlr
class should be aware of all its subclasses and include their header files, to create their objects.
CMsgHndlr
class is responsible for creating message handler objects based on the key passed to GetMsgHndlr
method and this needs to be changed when a new message handler is added.
- Application is forced to choose the required message handler class at link time.
Singletons using RTTI
Run-time type information ( RTTI ) is a mechanism to determine the type of an object during program execution. RTTI is now available as part of C++ language. This approach uses MFC’s CRuntimeClass
to identify the class of object dynamically and create it when needed ( Listing 3 ).
CObject, the root class of MFC provides the support for Run-time class information and dynamic object creation. To use these services, CMsgHndlr
class should be inherited from CObject
, DECLARE_DYNCREATE( CMsgHndlr )
macro must be included in the header file and IMPLEMENT_DYNCREATE( CMsgHndlr, CObject )
macro must be added to the implementation file.
GetMsgHndlr method takes CRuntimeClass * as parameter. It ensures that the given Runtime class is of type CMsgHndlr and returns the created object. RUNTIME_CLASS( class_name ) macro can be used to get CRuntimeClass information for a given C++ class.
Advantage
GetMsgHndlr
can create object of CMsgHndlr or its descendants just with their Runtime class information, without having to know their exact class names. This method need not change when a new message handler subclass is created.
Disadvantage
Choice of the message handler class is made at link time. If a new message handler class needs to be used, the application must pass the appropriate run time class information to GetMsgHndlr
.
Self-Registering Singletons
Let’s take an example of an application making the choice of message handler at runtime based on a key value set in the Registry or value passed as a Command Line Argument. Since, the selection of message handler is made at runtime, the application should be aware of all the available message handlers to select the appropriate one based on the runtime parameter, using a series of if statements. ( Listing 4 )
Listing 4 shows that, the code which checks and creates the message handler is moved from CMsgHndlr
( Listing 2 ) to SelectMsgHndlr
function in the application ( Listing 4 ). This kind of function should be written in all the applications using message handler. To complicate the situation further, let us assume that the message handlers are contained in a shared library or a DLL and the application is not aware of all available message handlers ( new message handler classes may be added in future to the library ). How can we select the right message handler ? It is simple. Each message handler class should register their runtime class information in a Message Handler Registry. This runtime information is used to create the required message handler object when needed. I have extended the idea of "Self-Registering, Objects in C++" by Jim Beveridge ( Dr. Dobb’s Journal, August 1998 ), to register the runtime class information. ( Listing 5 )
CObjRegistry
maintains a registry object, in which the runtime class information is stored. CMsgHndlrRegistry
is a template class inherited from CObjRegistry
. Message handler classes register their runtime class information to CObjRegistry via CMsgHndlrRegistry
. GetMsgHndlr method takes a key as a parameter, gets the required runtime class information from CObjRegistry and creates the required message handler object.
Advantage
- The class information of the message handlers are registered only at runtime, so a new message handler class can be added without affecting other pieces of the code.
- Choice of the message handler class can be made at run time. If a new message handler class needs to be used, it can be specified to the application through Registry or as a Command Line Argument, without changing the application.
Who’s going to Bell the Cat ?
Object cleanup is a problem with Singleton. Message handler object is created on the heap by GetMsgHndlr
method, but who’s going to delete it ? Strictly speaking, the application or the client module should delete the object after its use. It will result in a memory leak, if the object is not deleted. So, I feel that CMsgHndlr
should be smart enough to take care of its own cleanup.
Smart Singletons
Listing 6 contains smart message handler that takes care of its cleanup. CMsgHndlr
class employs a Smart Cleaner ( CSmartCleaner
) to do the cleanup job. It declares a static data member of CSmartCleaner
class and says "Hello" to CSmartCleaner
through SetObject( this )
during construction and says "Goodbye" through SetObject( NULL )
during destruction. When an application terminates, C++ will not do an automatic cleanup of the objects allocated on heap, but cleans up the static objects. So, the static CSmartCleaner
object gets destructed at the end of application and the destructor of CSmartCleaner
deletes the message handler object, if the application forgets to delete it.
Benefits of Singleton
- Singleton ensures that only one object of the class type is created and provides a global access to this object.
- Singleton classes can be used to create an object of its derived class, and the applications can choose the required singleton object without much change in the code.
- Singleton eliminates the need for a global variable.
Beyond Singleton
Even though Singleton is the main topic of discussion, this article introduces the following techniques to C++ developers.
- An approach to use MFC’s RTTI support for dynamic object creation.
- An approach for self-registering objects.
- An approach for object cleanup ( smart cleaner ), that can be used by a class to make sure that its instances are cleaned up properly.
- An approach that can be extended to create more than one object of a class in a controlled fashion.
Conclusion
Singleton classes are used in well-known applications. MFC’s CWinApp
is a good example, it ensures that an application has only object of CWinApp
type. Smalltalk’s metaclass is another example for a singleton class. A metaclass is the class of a class and there is only one instance of a metaclass for providing class level services.
This article used a message handler example to introduce the concept of Singletons. It walked through the different ways of implementing Singletons with their advantages and disadvantages. It started with a simple approach for creating Singletons using static variables, then it explained how an object of a Singleton class ( or its sub class ) could be created on heap. It also showed how the other two approaches - Singletons using RTTI and Self Registering Singletons, can be implemented using Visual C++ RTTI support. These two approaches can also be implemented using any other C++ compiler that has RTTI support. Smart Singletons presented an idea for object cleanup and showed how it can take care of its own cleanup.
Acknowledgments
Special thanks to my friend Sree Meenakshi for her helpful suggestions in improving the clarity and presentation of this article.
Listing 1 - Message Handler using static variables
class CMsgHndlr
{
public :
static CMsgHndlr * GetMsgHndlr();
protected :
CMsgHndlr(){}
virtual ~CMsgHndlr(){}
static CMsgHndlr m_MsgHndlr;
};
CMsgHndlr CMsgHndlr::m_MsgHndlr;
CMsgHndlr * CMsgHndlr::GetMsgHndlr()
{
return & m_MsgHndlr;
}
Listing 1a - Variation of GetMsgHndlr using Function static members instead of class static
CMsgHndlr * CMsgHndlr::GetMsgHndlr()
{
static CMsgHndlr MsgHndlr;
return & MsgHndlr;
}
Listing 2 - Message Handler on heap
class CMsgHndlr
{
public :
static CMsgHndlr * GetMsgHndlr( LPCSTR );
virtual ~CMsgHndlr(){}
protected :
CMsgHndlr(){}
static CMsgHndlr * m_pMsgHndlr;
};
CMsgHndlr * CMsgHndlr::m_pMsgHndlr = NULL;
CMsgHndlr * CMsgHndlr::GetMsgHndlr( LPCSTR lpszKey )
{
if(m_pMsgHndlr == NULL )
{
if( stricmp( lpszKey, "MSGHNDLR" ) == 0 )
{
m_pMsgHndlr = new CMsgHndlr;
}
else if( stricmp( lpszKey, "SMPHNDLR" ) == 0 )
{
m_pMsgHndlr = new CSmpHndlr;
}
}
return m_pMsgHndlr;
}
class CMsgHndlr : public CObject
{
public :
static CMsgHndlr * GetMsgHndlr( CRuntimeClass * );
DECLARE_DYNCREATE( CMsgHndlr )
virtual ~CMsgHndlr(){}
protected :
CMsgHndlr(){}
static CMsgHndlr * m_pMsgHndlr;
};
IMPLEMENT_DYNCREATE( CMsgHndlr, CObject )
CMsgHndlr * CMsgHndlr::m_pMsgHndlr = NULL;
CMsgHndlr * CMsgHndlr::GetMsgHndlr( CRuntimeClass * pRuntimeClass )
{
ASSERT( pRuntimeClass != NULL );
if( m_pMsgHndlr != NULL )
{
return m_pMsgHndlr;
}
CRuntimeClass * pBaseClass = RUNTIME_CLASS( CMsgHndlr );
if(pRuntimeClass ->IsDerivedFrom( pBaseClass ) == FALSE )
{
return NULL;
}
m_pMsgHndlr = ( CMsgHndlr * ) pMsgHndlrClass->CreateObject();
return m_pMsgHndlr;
}
Listing 4 -Selecting message handler using the key
CMsgHndlr * SelectMsgHndlr( LPCSTR lpszKey )
{
if( stricmp(lpszKey, "MSGHNDLR" ) == 0 )
{
return CMsgHndlr::GetMsgHndlr( RUNTIME_CLASS( CMsgHndlr ) );
}
else if( stricmp( lpszKey, "SMPHNDLR" ) == 0 )
{
return CMsgHndlr::GetMsgHndlr( RUNTIME_CLASS( CSmpHndlr ) );
}
return NULL;
}
class CObjRegistry
{
public :
CObjRegistry( CRuntimeClass * pRuntimeClass, LPCSTR lpszKey )
{
ASSERT( pRuntimeClass != NULL );
ASSERT( lpszKey != NULL );
GetRegistry().SetAt( lpszKey, pRuntimeClass );
}
static BOOL GetRuntimeClass( LPCSTR lpszKey,
CRuntimeClass * & rpRuntimeClass )
{
return GetRegistry().Lookup( lpszKey,
( LPVOID & ) rpRuntimeClass );
}
private :
static CMapStringToPtr & GetRegistry()
{
static CMapStringToPtr Registry;
return Registry;
}
};
template <class T> class CMsgHndlrRegistry : public CObjRegistry
{
public :
CMsgHndlrRegistry( CRuntimeClass * pRuntimeClass, LPCSTR lpszKey ) :
CObjRegistry( pRuntimeClass, lpszKey ) { }
};
static CMsgHndlrRegistry< CMsgHndlr > gMsgHndlr( RUNTIME_CLASS( CMsgHndlr ),
"MSGHNDLR" );
CMsgHndlr * CMsgHndlr::GetMsgHndlr( LPCSTR lpszKey )
{
CRuntimeClass * pRuntimeClass = NULL;
if( CObjRegistry::GetRuntimeClass( lpszKey, pRuntimeClass ) == FALSE )
{
return NULL;
}
return GetMsgHndlr( pRuntimeClass );
}
static CMsgHndlrRegistry< CSmpHndlr > gSmpHndlr( RUNTIME_CLASS( CSmpHndlr ),
"SMPHNDLR" );
CMsgHndlr * SelectMsgHndlr( LPCSTR lpszKey )
{
return CMsgHndlr::GetMsgHndlr( lpszKey );
}
class CMsgHndlr : public CObject
{
protected :
CMsgHndlr()
{
m_SmartCleaner.SetObject( this );
}
static CMsgHndlr * m_pMsgHndlr;
public :
static CMsgHndlr * GetMsgHndlr( CRuntimeClass * );
virtual ~CMsgHndlr()
{
m_SmartCleaner.SetObject( NULL );
CMsgHndlr::m_pMsgHndlr = NULL;
}
private :
static CSmartCleaner m_SmartCleaner;
};
class CSmartCleaner
{
public :
CSmartCleaner( CObject * pObject = NULL ) : m_pObject( pObject ) { }
virtual ~CSmartCleaner()
{
if( m_pObject != NULL )
{
delete m_pObject;
}
}
void SetObject( CObject * pObject )
{
m_pObject = pObject;
}
CObject * GetObject()
{
return m_pObject;
}
private :
CObject * m_pObject;
};
Listing 5 - Self-Registering SingletonsListing 3 - Message Handler using RTTI