Let's Talk About... Singleton
Written by Stefan Ruck
When I read about the Singleton design pattern (see [1]) for the first time, I was really enthusiastic about it. But when I started to use it, some doubts occurred to me. These are the results of my reflections.
What's A Singleton?
The main intention of a Singleton class is to make sure to have only one instance of this class with a global point of access to it, thus avoiding the use of global variables.
Gamma et al gave a printer spooler as an example. There may be many printers defined in a operating system, but there should only be one printer spooler.
I will use the Singleton design pattern in conjunction with initialization files (see [2]). In any program you need just one instance of your initialization file. So why not design the initialization file class as a Singleton. Therefore I will expand the sample program of [2] later on.
The declaration of a typical Singleton class is as follows:
<small> class Singleton { public: static Singleton * instance (); protected: Singleton (); private: static Singleton * m_pInstance; }; </small>
Figure 1: Declaration of a Singleton class,
Its implementation looks like this:
<small> Singleton * Singleton :: m_pInstance = 0L; Singleton * Singleton :: instance () { return ((m_pInstance ? m_pInstance : (m_pInstance = new Singleton))); } </small>
Figure 2: Implementation of a Singleton class,
Because the constructor is not of interest, it's not shown here.
Some explanation to those of you who aren't familiar with the ? operator. In spoken words the code in Singleton::instance is:
If m_pInstance is not null, return m_pInstance, else assign the address of the new allocated Singleton object to m_pInstance and return m_pInstance.
Like it or not, it works fine (and I like it a lot).
What Is The Problem?
Maybe you will see there is no way to delete the Singleton object created during the first call of Singleton::instance.
When you use a Singleton class e.g. in a Microsoft Visual C++ 4.x environment in debug mode, you will get a warning about memory leaks when you close the debugger. That's correct because there is no corresponding delete to the new in Singleton::instance.
OK, you can say: Why bother, as soon as the program terminates, all allocated memory will be released.
I think it's not a good programming style when there is no delete to a new. So using Singleton in the original manner, it seems to be incomplete to me.
So what can we do?
Three Steps To Heaven
1st Step: Singleton::destroyInstance
The implementation of Singleton::destroyInstance is quite easy.
<small> public: static void destroyInstance (); void Singleton :: destroyInstance () { delete m_pInstance; m_pInstance = 0L; } </small>
Figure 3: Declaration and implementation of Singleton::destroyInstance,
The question is: When do we call Singleton::destroyInstance? The safest place to call it is inside of the destructor of the main application class. This works fine as long as you remember not to use it somewhere else and none of your colleagues is going to use this class. But as soon as you call it somewhere else, you can't be sure you won't destroy the instance that's used by another class which is storing the pointer returned by Singleton::instance in a class member.
So I think this isn't a safe way to destroy the allocated instance.
2nd Step: Reference Counting
What we can do is to increment a counter any time Singleton::instance is called and decrement it when Singleton::destroyInstance is called. Only when the counter is zero, will the instance be deleted. This makes sure the Singleton object is destroyed only when it is not used any more. And because destroyInstance isn't the right name anymore, I will call it releaseInstance from now on.
This leads to the following changes:
<small> public: static void releaseInstance (); private: static unsigned long m_ulReferenceCounter; unsigned long Singleton :: m_ulReferenceCounter = 0L; Singleton * Singleton :: instance () { if (!m_pInstance) m_pInstance = new Singleton; ++m_ulReferenceCounter; return (m_pInstance); } </small>
Figure 4a: Changes in Singleton for reference counting,
Singleton::instance increases Singleton::m_ulReferenc