In this article, I provide a template class that will be useful as it takes away the need for thread synchronization and reference counting.
Introduction
Singleton is a pattern which provides a design strategy which helps to control the instance creation of a class. It helps to maintain only a single instance (or variable instance numbers) of a class by making the constructor private
and providing a instance creation function.
Singleton is a very widely used pattern. Well, at the same time, it can be implemented in different ways. The pattern specification says only a single instance or a controlled number of instances can be created. If we consider a C++ version of singleton, there are multiple ways in which we can implement the pattern. Another consideration is multithreading. We will consider the different design strategies and see how we can implement a thread safe singleton object.
The Simplest Design
See the below code snippet to implement a singleton:
class XMLReader
{
public:
XMLReader& GetInstance()
{
static XMLReader XMLReaderObj;
return XMLReaderObj;
}
private:
XMLReader(){}
};
This is the simplest form of singleton implementation in C++. A static
local object is created and its reference is returned. Since the static
objects live in global memory, it will live till the process ends. Here, we cannot control the lifetime of the object. It gets destructed when the application main returns. Suppose we need to do some cleanup before the main
returns. For example, we need to save some information to an XML file. We can use an XML library like MSXML for managing XML files. This library is COM based. We initialize COM using CoInitialize
or CoInitializeEx
once per thread and call CoUninitialize
before the thread returns. See the full source code for XMLWriter
below:
class XMLWriter
{
public:
static XMLWriter& GetInstance()
{
static XMLWriter SingletonObj;
return SingletonObj;
}
bool UpdateXML(const DataHolder& DataHolderObj)
{
}
~XMLWriter()
{
_bstr_t bstrFileName(_T("E:\\Test.xml"));
m_pXMLDom->save( bstrFileName );
}
private:
XMLWriter()
{
InitDOM();
}
void InitDOM()
{
HRESULT hr = m_pXMLDom.CreateInstance(__uuidof(MSXML2::DOMDocument60));
if (SUCCEEDED(hr))
{
m_pXMLDom->loadXML(
_bstr_t(_T("<?xml version='1.0'?>\n")
_T("<doc title='test'>\n")
_T(" <page num='1'> \n")
_T(" <para title='Saved at last'> \n")
_T(" This XML data is finally saved.\n")
_T(" </para>\n")
_T(" </page>\n")
_T(" <page num='2'>\n")
_T( " <para>\n")
_T(" This page is intentionally left blank.\n")
_T(" </para>\n")
_T(" </page>\n")
_T("</doc>\n")));
}
}
private:
MSXML2::IXMLDOMDocumentPtr m_pXMLDom;
};
void main()
{
CoInitialize(0);
XMLWriter& WriterObj = XMLWriter::GetInstance();
CoUninitialize();
}
Certainly, this code will break the application and it will crash on exit. If we look at the destructor of XMLWriter
, it is trying to save the loaded XML into a file. But before the main
returns, we have uninitialized COM and the saving code needs COM environment. When the main returns, the destructor gets kicked and code to save file executes. Bang!!!
We can make the call to CoInitialize
in the constructor and CoUninitialize
in the destructor. But in a multi threaded application, we can’t guarantee in which thread's context the object is created and so CoInitialize
is called. So, the CoInitialize
may get called in one worker thread and CoUninitialize
in the main thread (inside the destructor) as the application exits. In fact, the main
thread has not called a matching CoInitialize
. This can cause undefined results.
This is a simple example to show that we need control on the destruction process. When there is nothing to be done while the object is destroyed, it is OK to use the local static object. In fact, it is a threadsafe implementation. The compiler will do the necessary that a single instance of the static
object is created when used in a multithreaded environment.
Now, we will look into other strategies in singleton implementation.
Creator and Destroyer Methods for Finer Control
See the below code snippet:
class XMLReader
{
public:
static XMLReader& CreateInstance()
{
if(!m_pXMLReader)
{
m_pXMLReader = new XMLReader;
}
return (*m_pXMLReader);
}
static XMLReader& GetInstance()
{
return (*m_pXMLReader);
}
static void DestroyInstance()
{
delete m_pXMLReader;
}
~XMLReader()
{
}
private:
XMLReader()
{
}
private:
static XMLReader* m_pXMLReader;
};
This code seems OK. But what happens if memory allocation failure happens in CreateInstance
. It will be returning a NULL
reference if new returns NULL
. If the allocation failure is returned as std::bad_alloc
, CreateInstance
can throw an exception, which the singleton object creation code should be aware of. In the case of new returning NULL
, GetInstance
also returns a NULL
reference, which can lead to a runtime error. A modified version of this implementation is shown below:
class XMLReader
{
public:
static XMLReader& CreateInstance()
{
try
{
if(!m_pXMLReader)
{
m_pXMLReader = new XMLReader;
}
return (*m_pXMLReader);
}
catch(std::bad_alloc& baException)
{
throw new ObjectCreationFailed(_T("Memory allocation failed"));
}
catch(...)
{
throw new ObjectCreationFailed(_T("Unknown reason"));
}
}
static XMLReader& GetInstance()
{
if(!m_pXMLReader)
{
throw new ObjectDoesNotExist;
}
return (*m_pXMLReader);
}
static void DestroyInstance()
{
delete m_pXMLReader;
}
~XMLReader()
{
}
private:
XMLReader()
{
}
private:
static XMLReader* m_pXMLReader;
};
The Multi Threading Aspects
In the above version, if any error occurs in the object creation, the object itself will not be created. This is a much better one. Now, what about thread safety aspects. What happens if CreateInstance
is called simultaneously by two threads? Certainly, there is a possibility of multiple instances created. This violates the assumption of singleton of only a sole instance. How can we solve this?
The solution is very common in the multithreaded environment, we need to synchronize the object creation using a critical section or mutex. We shall discuss a complete solution in the coming sections.
The usage of the XMLReader
class will be as shown below:
XMLReader& XMLRdrObj =
XMLReader::CreateInstance();
XMLReader::DestroyInstance();
Suppose we have multiple child threads and both need the same singleton class. Probably, we need to call CreateInstance()
from thread1
and 2 (say). Similarly, DestroyInstance
before the threads end. But, how will we ensure that both threads see a valid instance always. What if thread1
ends first and thread2
continues to run? It will be catastrophic if thread1
invokes DestroyInstance
and thread2
will be using an already deleted instance. This problem suggests that we need a reference counting mechanism, so that the object will be destroyed only when the final DestroyInstance
is invoked. See the following implementation:
class XMLReader
{
public:
static XMLReader& CreateInstance()
{
try
{
if(!m_pXMLReader)
{
m_pXMLReader = new XMLReader;
}
++m_nRefCount;
return (*m_pXMLReader);
}
catch(std::bad_alloc& baException)
{
throw new ObjectCreationFailed(_T("Memory allocation failed"));
}
catch(...)
{
throw new ObjectCreationFailed(_T("Unknown reason"));
}
}
static XMLReader& GetInstance()
{
if(!m_pXMLReader)
{
throw new ObjectDoesNotExist;
}
return (*m_pXMLReader);
}
static void DestroyInstance()
{
--m_nRefCount;
if(0 == m_nRefCount)
delete m_pXMLReader;
}
~XMLReader()
{
}
private:
XMLReader()
{
}
private:
static int m_nRefCount;
static XMLReader* m_pXMLReader;
};
This seems much better. We have one more pending issue to be solved, a thread safe instance creation. Here, I am introducing a complete solution which is a template class, that can be reused to make singleton classes.
The Solution
See the code below:
#pragma once
#include <exception>
class CriticalSectionObject
{
public:
CriticalSectionObject()
{
InitializeCriticalSection(&m_stCriticalSection);
}
void Lock()
{
EnterCriticalSection(&m_stCriticalSection);
}
void UnLock()
{
LeaveCriticalSection(&m_stCriticalSection);
}
~CriticalSectionObject()
{
DeleteCriticalSection(&m_stCriticalSection);
}
private:
CRITICAL_SECTION m_stCriticalSection;
};
class AutoCriticalSection
{
public:
AutoCriticalSection(CriticalSectionObject* pCSObj)
{
m_pCSObj = pCSObj;
if( m_pCSObj )
{
m_pCSObj->Lock();
}
}
~AutoCriticalSection()
{
if( m_pCSObj )
{
m_pCSObj->UnLock();
}
}
private:
CriticalSectionObject* m_pCSObj;
};
extern CriticalSectionObject g_CSObj;
class GeneralException
{
public:
GeneralException(LPCTSTR lpctszDesciption)
{
m_ptcDescription = StrDup(lpctszDesciption);
}
~GeneralException()
{
LocalFree(m_ptcDescription);
}
TCHAR* GetDescription()
{
return m_ptcDescription;
}
private:
TCHAR* m_ptcDescription;
};
class ObjectCreationFailedException:public GeneralException
{
public:
ObjectCreationFailedException(LPCTSTR lpctszDesciption):GeneralException(lpctszDesciption)
{
}
};
class ObjectDoesNotExistException:public GeneralException
{
public:
ObjectDoesNotExistException():GeneralException(_T("Object does not exist"))
{
}
};
template <class T> class SingletonTemplate
{
public:
static T& CreateInstance()
{
try
{
AutoCriticalSection ACSObj(&g_CSObj);
if( 0 == m_pTSingleInstance )
{
m_pTSingleInstance = new T;
}
++m_nRefCount;
}
catch(std::bad_alloc)
{
throw new ObjectCreationFailedException(_T("Memory allocation failed"));
}
catch(...)
{
throw new ObjectCreationFailedException(_T("Unknown reason"));
}
return *m_pTSingleInstance;
}
static T& GetInstance()
{
if( 0 == m_pTSingleInstance )
{
throw new ObjectDoesNotExistException;
}
return *m_pTSingleInstance;
}
static void DestroyInstance()
{
AutoCriticalSection ACSObj(&g_CSObj);
(m_nRefCount > 0) ? --m_nRefCount:m_nRefCount;
if( 0 == m_nRefCount )
{
delete m_pTSingleInstance;
m_pTSingleInstance = 0;
}
}
private:
static T* m_pTSingleInstance;
static int m_nRefCount;
};
template <class T> T* SingletonTemplate<T>::m_pTSingleInstance = 0;
template <class T> int SingletonTemplate<T>::m_nRefCount = 0;
The following code shows how to use SingletonTemplate
class:
#pragma once
#include "SingletonTemplate.h"
class SequenceGenSingleton
{
friend class SingletonTemplate<SequenceGenSingleton>;
public:
~SequenceGenSingleton(void);
long GetNextSeqNum();
private:
SequenceGenSingleton(void);
SequenceGenSingleton(int nInitVal);
private:
long m_lSeqNum;
};
The following code shows the usage of this class:
SingletonTemplate<SequenceGenSingleton>::CreateInstance();
SequenceGenSingleton& SingleObj = SingletonTemplate<SequenceGenSingleton>::GetInstance();
SingleObj.GetNextSeqNum();
SingletonTemplate<SequenceGenSingleton>::DestroyInstance();
The SingletonTemplate
should be made the friend of the target singleton class (this gives access to the private
constructor).
DLLs and Singleton
In the case of usage from a DLL, the code can be as shown below:
BOOL WINAPI DllMain( IN HINSTANCE hDllHandle,
IN DWORD dwReason,
IN LPVOID lpvReserved )
{
switch ( dwReason )
{
case DLL_PROCESS_ATTACH:
SingletonTemplate<SequenceGenSingleton>::CreateInstance();
break;
case DLL_PROCESS_DETACH:
SingletonTemplate<SequenceGenSingleton>::DestroyInstance();
break;
}
}
Usage from a Thread
In the case of usage from a child thread, the code can be as shown below:
DWORD WINAPI MyWorkerThread(LPVOID)
{
SingletonTemplate<SequenceGenSingleton>::CreateInstance();
SingletonTemplate<SequenceGenSingleton>::DestroyInstance();
return 0;
}
The thread function can have multiple return paths. So we need to call DestroyInstance
in all return paths. This gives the possibility to define an automatic class that can automate the release process. It can be as shown below:
template <class T> class AutoSingleton
{
public:
AutoSingleton()
{
SingletonTemplate<T>::CreateInstance();
}
~AutoSingleton()
{
SingletonTemplate<T>::DestroyInstance();
}
};
AutoSingleton<SequenceGenSingleton> AutoSingletonObj;
SequenceGenSingleton& SingleObj = SingletonTemplate<SequenceGenSingleton>::GetInstance();
The wrapup
This is all that I have to offer now. I hope the template class provided will be useful as it takes away the need for thread synchronization and reference counting. We can make a singleton class which is thread safe just by parametrizing the SingletonTemplate
with the required class and making it a friend of your class.
History
- 26th September, 2012: Initial version