![]() |
Development Lifecycle »
Design and Architecture »
Design Patterns
Intermediate
License: The Code Project Open License (CPOL)
Creating Singleton Objects using Visual C++By T. Kulathu SarmaThis article discusses a Creational Pattern called Singleton and explains different approaches for implementing Singleton pattern using a Visual C++ example. |
C++/CLI, VC6, .NET, Win2K, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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.
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
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.
Class of the object CMsgHndlr is hard coded and the application cannot use a different message handler, without changing the class.
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.
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.
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. 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.
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.
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.
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
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.
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.
Even though Singleton is the main topic of discussion, this article introduces the following techniques to C++ developers.
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.
Special thanks to my friend Sree Meenakshi for her helpful suggestions in improving the clarity and presentation of this article.
class CMsgHndlr { public : static CMsgHndlr * GetMsgHndlr(); protected : CMsgHndlr(){} virtual ~CMsgHndlr(){} static CMsgHndlr m_MsgHndlr; // Static instance of Message handler // class. }; CMsgHndlr CMsgHndlr::m_MsgHndlr; CMsgHndlr * CMsgHndlr::GetMsgHndlr() { return & m_MsgHndlr; }
CMsgHndlr * CMsgHndlr::GetMsgHndlr()
{
static CMsgHndlr MsgHndlr; // Create object, when called for the first
// time
return & MsgHndlr;
}
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; }
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;
}
// Object Registry class to store Runtime class information 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; } }; // Message handler registry class, to which Message handlers get registered template <class T> class CMsgHndlrRegistry : public CObjRegistry { public : CMsgHndlrRegistry( CRuntimeClass * pRuntimeClass, LPCSTR lpszKey ) : CObjRegistry( pRuntimeClass, lpszKey ) { } }; // Message handler registration and GetMsgHndlr method in CMsgHndlr static CMsgHndlrRegistry< CMsgHndlr > gMsgHndlr( RUNTIME_CLASS( CMsgHndlr ), "MSGHNDLR" ); CMsgHndlr * CMsgHndlr::GetMsgHndlr( LPCSTR lpszKey ) { CRuntimeClass * pRuntimeClass = NULL; if( CObjRegistry::GetRuntimeClass( lpszKey, pRuntimeClass ) == FALSE ) { return NULL; } // Call GetMsgHndlr in listing 3 with runtime class information return GetMsgHndlr( pRuntimeClass ); } // Message handler registration code in CSmpHndlr static CMsgHndlrRegistry< CSmpHndlr > gSmpHndlr( RUNTIME_CLASS( CSmpHndlr ), "SMPHNDLR" ); // Application code to select the message handler 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
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 8 Jan 2001 Editor: Chris Maunder |
Copyright 2001 by T. Kulathu Sarma Everything else Copyright © CodeProject, 1999-2009 Web12 | Advertise on the Code Project |