C++ In Theory: The Singleton Pattern, Part I - The Gamma Singleton
(Page 4 of 5 )
It is clear that singletons are not very flexible when we have to rely on static variables and functions. But I have digressed a little, so let's have a look at how the Log class would be implemented as a singleton in "Design Patterns" [Gamma].
class Log {
public:
static Log* Instance() {
if (!m_pInstance)
m_pInstance = new Log;
return m_pInstance;
}
void Write(char const *logline);
bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
static Log* m_pInstance;
static std::list<std::string> m_data;
};
// in log.cpp we have to add
Log* Log::m_pInstance = NULL;
The constructor and copy constructor are hidden because they are made private, so no user has access to them except for the class itself. The only way to instantiate a Log is by calling Instance() which checks whether or not this class has already been instantiated. This seems like the perfect way to restrict access to our class, and we are sure that only one is ever created during the lifetime of our application!
“Seems?” I hear you think. If you look closely, you will notice that m_pInstance is never destructed. Depending on the definition you use for memory leakage, this class is actually leaking memory.
Some consider a memory leak to be an accumulation of data, of which you lose the references (and thus the ability to free them) during runtime. In this case m_pInstance is holding on to the memory right up to the end of the application, so does not have to be considered a memory leak. Since most operating systems free the memory a process has been using when it terminates, m_pInstance can be considered not to be leaking.
You should not depend on the operating system to clean up the memory you have allocated, especially not when your code has to be platform independent. There is at least one OS out there that doesn’t free the memory left allocated by a terminated process (I don't remember which one…most probably it is the OS/400) and if your singleton opened a file, socket or other system resources, these may remain locked after the process has closed and you will have created a resource leak.
Maybe we should have coded our Log singleton this way:
class Log {
public:
static Log* Instance() {
return &m_pInstance;
}
void Write(char const *logline);
bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
static Log m_pInstance;
static std::list<std::string> m_data;
};
// in log.cpp we have to add
Log Log::m_pInstance;
This solution loses an important benefit that our previous singleton did have: dynamic initialization. It is useful for your application to only create the singleton when it is needed/called upon. With our polymorphic example, every class would be instantiated and we would have to instantiate all forms of logging (even when this might not be possible!), while only using one of them!
Static initialization remains a problem as well. There is no guaranteed sequence in which statics are created and destroyed…which will cause problems for our logging class, but we will take a more detailed look at this later on.
Next: The Meyers Singleton >>
More C++ Articles
More By J. Nakamura