Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Variations on Singleton

Any static member object inside a class is an expression of Singleton: one and only one will be made. So in a sense, the language has direct support for the idea; we certainly use it on a regular basis. However, there s a problem with static objects (member or not): the order of initialization, as described in Volume 1 of this book. If one static object depends on another, it s important that the objects are initialized in the correct order.

In Volume 1, you were shown how to control initialization order by defining a static object inside a function. This delays the initialization of the object until the first time the function is called. If the function returns a reference to the static object, it gives you the effect of a Singleton while removing much of the worry of static initialization. For example, suppose you want to create a log file upon the first call to a function that returns a reference to that log file. This header file will do the trick:

//: C10:LogFile.h
#ifndef LOGFILE_H
#define LOGFILE_H
#include <fstream>
std::ofstream& logfile();
#endif // LOGFILE_H ///:~
 

The implementation must not be inlined because that would mean that the whole function, including the static object definition within, could be duplicated in any translation unit where it s included, which violates C++ s one-definition rule.[137] This would most certainly foil the attempts to control the order of initialization (but potentially in a subtle and hard-to-detect fashion). So the implementation must be separate:

//: C10:LogFile.cpp {O}
#include "LogFile.h"
std::ofstream& logfile() {
static std::ofstream log("Logfile.log");
return log;
} ///:~
 

Now the log object will not be initialized until the first time logfile( ) is called. So if you create a function:

//: C10:UseLog1.h
#ifndef USELOG1_H
#define USELOG1_H
void f();
#endif // USELOG1_H ///:~
 

that uses logfile( ) in its implementation:

//: C10:UseLog1.cpp {O}
#include "UseLog1.h"
#include "LogFile.h"
void f() {
logfile() << __FILE__ << std::endl;
} ///:~
 

And you use logfile( ) again in another file:

//: C10:UseLog2.cpp
//{L} LogFile UseLog1
#include "UseLog1.h"
#include "LogFile.h"
using namespace std;
void g() {
logfile() << __FILE__ << endl;
}
 
int main() {
f();
g();
} ///:~
 

the log object doesn t get created until the first call to f( ).

You can easily combine the creation of the static object inside a member function with the Singleton class. SingletonPattern.cpp can be modified to use this approach:[138]

//: C10:SingletonPattern2.cpp
// Meyers Singleton.
#include <iostream>
using namespace std;
 
class Singleton {
int i;
Singleton(int x) : i(x) { }
void operator=(Singleton&);
Singleton(const Singleton&);
public:
static Singleton& instance() {
static Singleton s(47);
return s;
}
int getValue() { return i; }
void setValue(int x) { i = x; }
};
 
int main() {
Singleton& s = Singleton::instance();
cout << s.getValue() << endl;
Singleton& s2 = Singleton::instance();
s2.setValue(9);
cout << s.getValue() << endl;
} ///:~
 

An especially interesting case occurs if two Singletons depend on each other, like this:

//: C10:FunctionStaticSingleton.cpp
 
class Singleton1 {
Singleton1() {}
public:
static Singleton1& ref() {
static Singleton1 single;
return single;
}
};
 
class Singleton2 {
Singleton1& s1;
Singleton2(Singleton1& s) : s1(s) {}
public:
static Singleton2& ref() {
static Singleton2 single(Singleton1::ref());
return single;
}
Singleton1& f() { return s1; }
};
 
int main() {
Singleton1& s1 = Singleton2::ref().f();
} ///:~
 

When Singleton2::ref( ) is called, it causes its sole Singleton2 object to be created. In the process of this creation, Singleton1::ref( ) is called, and that causes the sole Singleton1 object to be created. Because this technique doesn t rely on the order of linking or loading, the programmer has much better control over initialization, leading to fewer problems.

Yet another variation on Singleton separates the Singleton-ness of an object from its implementation. This is achieved using the Curiously Recurring Template Pattern mentioned in Chapter 5:

//: C10:CuriousSingleton.cpp
// Separates a class from its Singleton-ness (almost).
#include <iostream>
using namespace std;
 
template<class T> class Singleton {
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
protected:
Singleton() {}
virtual ~Singleton() {}
public:
static T& instance() {
static T theInstance;
return theInstance;
}
};
 
// A sample class to be made into a Singleton
class MyClass : public Singleton<MyClass> {
int x;
protected:
friend class Singleton<MyClass>;
MyClass() { x = 0; }
public:
void setValue(int n) { x = n; }
int getValue() const { return x; }
};
 
int main() {
MyClass& m = MyClass::instance();
cout << m.getValue() << endl;
m.setValue(1);
cout << m.getValue() << endl;
} ///:~
 

MyClass is made a Singleton by:

1.  Making its constructor private or protected.

2.  Making Singleton<MyClass> a friend.

3.  Deriving MyClass from Singleton<MyClass>.

The self-referencing in step 3 may sound implausible, but as we explained in Chapter 5, it works because there is only a static dependency on the template argument in the Singleton template. In other words, the code for the class Singleton<MyClass> can be instantiated by the compiler because it is not dependent on the size of MyClass. It s only later, when Singleton<MyClass>::instance( ) is first called, that the size of MyClass is needed, and by then MyClass has been compiled and its size is known.[139]

It s interesting how intricate such a simple pattern as Singleton can be, and we haven t even addressed issues of thread safety. Finally, Singleton should be used sparingly. True Singleton objects arise rarely, and the last thing a Singleton should be used for is to replace a global variable.[140]

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire