C++ In Theory: The Singleton Pattern, Part I
(Page 1 of 5 )
Have you ever wondered how to implement a class with simple logging functionality? J. Nakamura explains how to do it in a way that makes use of the Singleton pattern.
There is a great book called “Design Patterns” [Gamma] that encapsulates knowledge trapped inside the heads of experienced programmers, presenting it in understandable and useable chunks. Solutions to a lot of design problems often turn out to be the same, even when programmers discover them independently. Instead of providing these solutions to you in code format, the “Design Patterns” book presents the thoughts and reasoning behind the solutions along with example code.
Design Patterns are a great way to get a head start on solving that problem you have encountered or might yet have to encounter. In this article we are going to implement a class with simple logging functionality that will make use of the Singleton pattern. My intention is not to present you with a class that will end all your logging woes, but rather to use it to demonstrate why you need this pattern.
Writing to a log is an action where it makes sense to have only one single log object active at any time you want to register data with it. It doesn’t matter which part of your application is writing to the log, as long as all parts are using the same log.
In an older programming language such as C you might choose global variables and global functions to implement such facilities, but as a project grows larger the risk of adding subtle bugs grows as well. These global variables and global functions all end up in the global namespace, and the introduction of a foreign library (or any new code for that matter) might cause naming collisions (meaning there are multiple variables/functions sharing the same name/identifier). Such collisions can only be resolved by changing the names of your own global variables and/or functions; these changes are bug prone and quite a chore in large projects with a lot of source files.
The benefit of the Singleton pattern (which can be implemented in C++) is that it prevents you from filling up the global namespace. You can keep the variables and functions locked up inside a class and restrict access to it by implementing a single point of access, which prevents you from creating multiple instances of the class. Other great features of the C++ language such as polymorphism (changing the behavior of a class through virtual functions) remain available to you as well.
C++ In Theory: The Singleton Pattern, Part I - A Logging Class
(Page 2 of 5 )
Here is a simple class that collects strings and can write them to a file. This class will represent the log we aim to implement in this article.
// log.h
#ifndef __LOG_H
#define __LOG_H#include <list>
#include <string>class Log {
public:
void Write(char const *logline);
bool SaveTo(char const *filename);
private:
std::list<std::string> m_data;
};
#endif // __LOG_H
// eof// log.cpp
#include <fstream>
using namespace std;#include “log.h”
void Log::Write(char const *logline)
{
m_data.push_back(logline);
}bool Log::SaveTo(char const *filename)
{
ofstream file(filename, ios::out);
list<string>::iterator it;
list<string>::iterator lastIt=m_data.end();
for (it=m_data.begin(); it!=lastIt; ++it)
file << *it << endl;
file.close();
}// eof
We are going to alter the behavior of this class to ensure that only one instantiation of it can be created during the lifetime of an application using it. This will cause all strings provided in calls to Log::Write, to end up in a single list (Log::m_data) which can be written to a single file when Log::Write is called.
C++ In Theory: The Singleton Pattern, Part I - Statics are not Singletons Forcing
the existence of only one instance of the Log class seems easily
implemented when we use only static variables and functions: class Log { In log.cpp we need to add std::list<std::string> Log::m_data; Though
it looks like it fulfills our goal, there are some downsides to these
kinds of singleton implementations. First of all, the construction and
destruction of static member variables are difficult to localize. They
are instantiated in any sequence by grace of the compiler, outside any
constructors. This can cause tremendous problems when singletons depend
on the right construction/destruction sequence Second, it is not
possible to implement polymorphic behavior this way because static
functions cannot be virtual. This means that a singleton implemented
with static variables/functions cannot be accessed through its
baseclass. For example, when our application runs standalone on a
machine we probably want to save the log to a file, but when it is part
of a distributed application we might want to save the log to a logging
server.
Instead of bundling all functionality into one big fat
"does-everything" class, we split the behavior up in to separate
classes, all implementing the interface of the abstract baseclass: // LogBase.h The
destructor of our baseclass has to be declared virtual, otherwise the
destructor of a derived class will not be called when it is destroyed
through the baseclass. // LogFile.h // LogNetwork.h It
is good to give each class its own responsibility, and to choose which
Log class you will use during runtime. This prevents us from having to
instantiate code we will not need: int main(int argc, char *argv[]) if (strcmp(argv[1], ”-StandAlone”)==0) This
is of course a fictional example, but I hope you get the idea of what
polymorphic behavior can mean for your application. In the example
above, application code can log through the LogBase class, but
depending on the arguments given during start-up, the logs will either
be written to file or to a network server. C++ In Theory: The Singleton Pattern, Part I - The Gamma Singleton 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* m_pInstance; // in log.cpp we have to add 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 { static Log m_pInstance; // in log.cpp we have to add 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. C++ In Theory: The Singleton Pattern, Part I - The Meyers Singleton Luckily there is another solution that is very easy to implement and we call it the Meyers Singleton [Meyers]: class Log { static std::list<std::string> m_data; This
construction relies on the fact that function-static objects are only
initialized when the function is first being called upon; thus we
maintain the benefit of dynamic initialization. (Function-static
primitive variables like static int number=100; do get translated
during compile time however!) An additional safeguard is the
returned reference instead of the pointer. The caller cannot be tempted
to delete the singleton (as was the case when we were returning a
pointer). The constructor is hidden, making it impossible for a user to
instantiate a Log object; it would still be possible to assign the
singleton Log to itself, however, and since this makes no sense we
might just as well hide the assignment operator. Log::Instance().Write(“Test line”); // works fine To wrap it all up, here is an abstract of the Meyers Singleton: class Singleton { In
the next article in this series, we will look at how to generalize this
class, and at the construction/destruction sequencing problems. REFERENCES [Gamma] Design Patterns: E.Gamma, R.Helm, R.Johnson and J.Vlissides.
(Page 3 of 5 )
public:
static void Write(char const *logline);
static bool SaveTo(char const *filename);
private:
static std::list<std::string> m_data;
};
class LogBase {
public:
virtual ~LogBase() {}
virtual void Write(char const *logline)=0;
virtual bool SaveTo(char const *filename)=0;
};
class LogFile : public LogBase {
public:
virtual ~LogFile();
virtual void Write(char const *logline);
virtual void SaveTo(char const *filename);
private:
std::list<std::string> m_data;
};
class LogNetwork : public LogNetwork {
public:
virtual ~LogNetwork();
virtual void Write(char const *logline);
virtual void SaveTo(char const *filename);
virtual void SetServer(TCPConnection const &server);
private:
TCPConnection m_logServer;
std::list<std::string> m_data;
};
{
LogBase *myLog = NULL;
if (argc == 1)
{
ShowUsage();
return –1;
}
myLog = reinterpret_cast<LogBase*>(new LogFile);
else if (strcmp(argv[1], “-Networked”)==0)
{
TCPConnection fictServer(“127.0.0.1:31280”);
LogNetwork *logPtr = new LogNetwork;
logPtr->SetServer(fictServer);
myLog = reinterpret_cast<LogBase*>(logPtr);
}
else
{
ShowUsage();
return –1;
}
/* more code here */
// logging example
myLog->Write(“test line going to either a file or network log.”);
}
(Page 4 of 5 )
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 std::list<std::string> m_data;
};
Log* Log::m_pInstance = NULL;
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 std::list<std::string> m_data;
};
Log Log::m_pInstance;
(Page 5 of 5 )
public:
static Log& Instance() {
static Log theLog;
return theLog;
}
void Write(char const *logline);
bool SaveTo(char const *filename);
private:
Log(); // ctor is hidden
Log(Log const&); // copy ctor is hidden
Log& operator=(Log const&); // assign op is hidden
};
Log &myLog = Log::Instance(); // works fine
Log another = myLog; // FAILS
myLog = myLog; // works until we hide the assign ops!
public:
static Singleton& Instance() {
static Singleton theSingleton;
return theSingleton;
}
/* more (non-static) functions here */
private:
Singleton(); // ctor hidden
Singleton(Singleton const&); // copy ctor hidden
Singleton& operator=(Singleton const&); // assign op. hidden
~Singleton(); // dtor hidden
};
[Meyers] More Effective C++: S.Meyers.
(Page 1 of 4 )
In our second article on the Singleton pattern, J. Nakamura discusses a reusable code version of the pattern.
In the previous article I introduced a design pattern named the “Singleton” as it is described in the book Design Patterns [Gamma]. Design patterns are very useful for describing solutions to common problems that software engineers often encounter. The Singleton pattern is a solution to the desire to have only one object of a class instantiated during the lifetime of your application.
Though design patterns were invented because solutions were often not reusable on the code level, the singleton pattern actually does have a reusable code version. We are going to look at a way to generalize the singleton implementation and how we can use the generalization to deal with construction/destruction sequence problems.
This topic will be covered in four sections:
- Singleton Generalization;
- Testing Our Generic Implementation;
- Singletons are not for Simpletons; and
- Resources.
Let's get started.
C++ In Theory: The Singleton Pattern, Part 2 - Singleton Generalization One
of the rules I try to live up to, is that every function and every
class should have a single purpose and responsibility. Bolting the
Instance() function onto any class you would like to behave as a
singleton (and hiding its constructor, destructor, copy constructor and
assignment operator) is pretty intrusive and adds additional purpose
and responsibility to the interface of that class. You might as well
end up with two different versions of the same class: one normal and
one singleton version. Would it not be nice to have a singleton class
that could add this behavior to any of our classes, whenever we desire
it, without having to make any changes? An object-oriented
programmer might be tempted to use class inheritance to solve this
problem. After all, if our class is a singleton, should it not be
derived from a singleton class? It is very tricky to get
Instance() to create the correct derived object without moving the
functionality down into the derived class, and we would still force any
derived class to hide the constructor, destructor and assignment
operator as well. Defining a separate singleton baseclass becomes a bit
superfluous (additional purpose and responsibility will still end up in
the derived class), except that it enforces a certain standard to be
followed. Additionally you most probably will have to use multiple
inheritance to get your implementation right and will probably have
multiple headaches (feel free to mail me if you would like me to write
about the downsides and upsides of multiple inheritance; diamonds and
extension interfaces are great fun!). Enough threats made… let's look at the solution first. // singleton.h template <class T> #endif This
implementation is so simple that I really love it. It implements the
singleton behavior that we can add onto any class T we provide as the
template parameter. To transform the Log class from the beginning
of the previous article to a singleton, without adding an Instance()
function and hiding the constructor, destructor etc, I could define it
this way: typedef Singleton<Log> LOG; And I can write to the log from any location in the application code (as long as it includes the necessary headers): LOG::Instance().Write(“This is a logline”); The
great benefit of the above implementation is that we never have to
change the singleton.h header file and can use it to transform any of
our classes into a singleton -- without changing them! C++ In Theory: The Singleton Pattern, Part 2 - Testing Our Generic Implementation Let's
run a little test to trace object construction and destruction when we
use our generic singleton implementation. We expect our singleton class
to be created only once! // main.cpp class MyClass { ~MyClass() { void foo(int value) { int bar() const private: int MyClass::sCount=0; typedef Singleton<MyClass> MyClassSingleton; void test1() { void test2() { int main(int argc, char *argv[]) { (void)printf(\n*** using singleton ***\n”); (void)printf(“\n--- DONE ---\n”); I
defined a MyClass trace object that prints a message when it is
constructed and when it is destructed. On top of that, every
instantiated MyClass receives an unique ID, so that we can see which
instantiated object is being manipulated. Running the code above will give you the following result: *** test1 *** *** using singleton *** *** test2 *** #and it stays alive! --- DONE --- Our
Singleton template has provided the means to transform MyClass into a
Singleton, following the definition/intent as provided in “Design
Patterns” [Gamma]: “Ensure a class only has one instance, and provide a
global point of access to it.” Great, isn’t it? Well it is...until you
start using multiple Singletons that depend on each other. C++ In Theory: The Singleton Pattern, Part 2 - Singletons are not for Simpletons While
the implementation looks deceptively simple and stable, it harbors a
pretty nasty problem: the sequence of static object destruction is not
given when an application is being terminated. Imagine two singletons A
and B using a logging class L (which is the singleton from the previous
article), and after A has used the log in a regular way, the other
singleton B causes the application to terminate. The construction
sequence might very well be A – L – C when C fails and terminates the
application. So C is destructed first, followed by the Log singleton L,
and finally singleton A is destroyed. There is no reason why A won’t
attempt to write to the Log while it is being destroyed (destructors
are a good location to dump the internal state of an object to the
log). But the Log instance was just destroyed! So
LOG::Instance().Write() will at best yield an exception or at worst
cause your application to terminate the hard way! The book Modern C++ Design
[Alexandrescu] describes this problem in detail and demonstrates
multiple solutions, from using the Phoenix Singleton to revive dead
singletons, to using Policy template classes to control a Singleton's
lifetime. The book is very advanced, very enjoyable and very useful. I
have chosen to demonstrate an easy way out to you for now. We will
implement a policy option to create either a Meyers Singleton or a
Gamma Singleton and leave it at that for now. If you have read
the previous article, you will know that the Meyers Singleton is an
implementation that prevents memory leakage. The Gamma Singleton, on
the other hand, doesn’t clean up after itself and this comes in handy
with our destruction sequence problem. The best way to guarantee our
singleton will remain alive to the very end is by simply not destroying
it! Please note that we will depend on the operating system to
clean up memory for us, and we have to be very careful not to create a
resource leak! Take a look at the Loki library [Alexandrescu] to find
out what the complete solution is when the ones I have given you won’t
suffice anymore (or when you are curious of course). It will be very
trivial to replace these singletons with the Loki ones. Our
singleton needs two different methods of creation. If we externalize
these creation methods from the singleton class and provide one of them
as a template parameter, the singleton class has become configurable as
far as its creation is concerned. This is what policies [Alexandrescu]
are all about. I have constructed a CreateGamma and a CreateMeyers class to demonstrate this concept: // singleton.h // // This is how a Gamma Singleton would instantiate its object. // This is how a Meyers Singleton would instantiate its object. // This Singleton class accepts different creation policies. private: static T* m_pInstance; template <class T, template<class> class C> #endif // __SINGLETON_H Now we can add the following code to our test in main.cpp: typedef Singleton<MyClass, CreateGamma> MyClassLeaking; void test3() // in main() And see that it yields the desired leakage: *** using leaking singleton *** --- DONE --- At
first I thought it would be easy to describe the singleton, until I
remembered the troubles I’ve had with construction and destruction
sequences of multiple interdependent singletons. Hopefully I have been
able to shed some insight into the usage of this "simple" design
pattern. References [Gamma] Design Patterns: E.Gamma, R.Helm, R.Johnson and J.Vlissides.
(Page 2 of 4 )
#ifndef __SINGLETON_H
#define __SINGLETON_H
class Singleton
{
public:
static T& Instance() {
static T _instance;
return _instance;
}
private:
Singleton(); // ctor hidden
~Singleton(); // dtor hidden
Singleton(Singleton const&); // copy ctor hidden
Singleton& operator=(Singleton const&); // assign op hidden
};
// eof
(Page 3 of 4 )
#include <stdio.h>
#include “singleton.h”
public:
MyClass()
: m_Value(0)
, m_ID(++sCount)
{
(void)printf(“MyClass #%d constructed.\n”, m_ID);
}
(void)printf(“MyClass #%d destructed.\n”, m_ID);
}
(void)printf(“MyClass%d::foo(0x%08X) called.\n”, m_ID, value);
m_Value=value;
}
{
(void)printf(“MyClass%d::bar() called.\n”, m_ID);
(void)printf(“… m_Value is: 0x%08X.\n”, m_Value);
return m_Value;
}
int m_Value;
int m_ID;
static int sCount;
};
MyClass myObj;
int val=myObj.bar();
myObj.foo(++val);
(void)myObj.bar();
}
int val=MyClassSingleton::Instance().bar();
MyClassSingleton::Instance().foo(++val);
(void)MyClassSingleton::Instance().bar();
}
(void)printf(“*** test1 ***\n”);
test1();
(void)printf(“*** test1 ***\n”);
test1();
MyClassSingleton::Instance().foo(0x0010);
(void)printf(“*** test2 ***\n”);
test2();
(void)printf(“*** test2 ***\n”);
test2();
return 0;
}
MyClass #1 constructed. #start lifetime MyClass #1 (normal)
MyClass1::bar() called.
... m_Value is: 0x00000000.
MyClass1::foo(0x00000001) called.
MyClass1::bar() called.
... m_Value is: 0x00000001.
MyClass #1 destructed. #end lifetime MyClass #1
*** test1 *** #.. we create another one
MyClass #2 constructed. #start lifetime MyClass #2 (normal)
MyClass2::bar() called.
... m_Value is: 0x00000000.
MyClass2::foo(0x00000001) called.
MyClass2::bar() called.
... m_Value is: 0x00000001.
MyClass #2 destructed. #end lifetime MyClass #2
MyClass #3 constructed. #start lifetime MyClass #3 (singleton)
MyClass3::foo(0x00000010) called. #constructed before we call test()
*** test2 ***
MyClass3::bar() called.
... m_Value is: 0x00000010.
MyClass3::foo(0x00000011) called.
MyClass3::bar() called.
... m_Value is: 0x00000011.
MyClass3::bar() called.
... m_Value is: 0x00000011.
MyClass3::foo(0x00000012) called.
MyClass3::bar() called.
... m_Value is: 0x00000012.
MyClass #3 destructed. #singleton destroyed at exit!
(Page 4 of 4 )
#ifndef __SINGLETON_H
#define __SINGLETON_H
// for a definite implementation of the singleton check out
// http://sourceforge.net/projects/loki-lib/
//
// or the book “Modern C++ Design” by A.Alexandrescu
//
template <class T> struct CreateGamma {
static T* Create() { return new T; }
};
template <class T> struct CreateMeyers {
static T* Create() {
static T _instance;
return &_instance;
}
};
template <class T, template<class> class CreationPolicy=CreateMeyers>
class Singleton {
public:
static T& Instance() {
if (!m_pInstance)
m_pInstance=CreationPolicy<T>::Create();
return *m_pInstance;
}
Singleton(); // ctor hidden
~Singleton(); // dtor hidden
Singleton(Singleton const&); // copy ctor hidden
Singleton& operator=(Singleton const&); // assign op hidden
};
T* Singleton<T,C>::m_pInstance=0;
// eof
{
int val=MyClassLeaking::Instance().bar();
MyClassLeaking::Instance().foo(++val);
(void)MyClassLeaking::Instance().bar();
}
(void)printf(“\n*** using leaking singleton ***\n”);
MyLeakingSingleton::Instance().foo(0x0100);
(void)printf(“*** test3 ***\n”);
test3();
(void)printf(“*** test3 ***\n”);
test3();
MyClass #4 constructed. #here our singleton is constructed
MyClass4::foo(0x00000100) called.
*** test3 ***
MyClass4::bar() called.
... m_Value is: 0x00000100.
MyClass4::foo(0x00000101) called.
MyClass4::bar() called.
... m_Value is: 0x00000101.
*** test3 *** #it lives fine across all calls
MyClass4::bar() called.
... m_Value is: 0x00000101.
MyClass4::foo(0x00000102) called.
MyClass4::bar() called.
... m_Value is: 0x00000102.
MyClass #3 destructed. #the Meyers Singleton is destroyed
#and our Gamma Singleton is still alive!
[Alexandrescu] Modern C++ Design: A.Alexandrescu.
[Meyers] More Effective C++: S.Meyers.
- More on Handling Basic Data Types (0)2007/07/27
- C++ in theory: Bridging Your Classes with PIMPLs (0)2007/07/27
- C++ In Theory: The Singleton Pattern (0)2007/07/27
- <iostream> vs. <iostream.h> (0)2007/07/03
- C 유저를 위한 C++ 팁 모음 (0)2007/04/30

수안이의 컴퓨터 연구실



Leave your greetings.