Hello, today I would like to share a nice article written by Sudheesh.P.S. I will just include the main parts of the article, if you would like to read the hole article:
http://www.codeproject.com/Articles/466255/Singleton-Pattern-Single-instance-but-multiple
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 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 most simplest form of
singleton implementation in C++. A static local object is created and its reference
is returned. Since the static objects lives in the 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 needs to save some information
to a 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 one time per thread and calls
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)
{
//XML updating code
}
~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("\n")
_T("\n")
_T(" \n")
_T(" \n")
_T(" This XML data is finally saved.\n")
_T("
\n") _T("
\n") _T(" \n")
_T( " \n")
_T(" This page is intentionally left blank.\n")
_T("
\n") _T("
\n") _T("
\n")));
}
}
private:
MSXML2::IXMLDOMDocumentPtr m_pXMLDom;
};
void main()
{
//Initialize COM
CoInitialize(0);
XMLWriter& WriterObj = XMLWriter::GetInstance();
///some code using ReaderObj
//...........
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 in to 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 guarentee 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. Infact, the mainthread have not called a
matching
CoInitialize. This can cause undefined results.
This is 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.
Infact 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 in to 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. In the case of new returning
NULL, GetInstance also return 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 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 the
XMLReader class
will be as shown below.
XMLReader& XMLRdrObj =
XMLReader::CreateInstance();
XMLReader::DestroyInstance();
Suppose we have multiple child
threads and both needs 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;
};