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, a problem is associated with static objects (member or not), and that’s 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
std::ofstream& logfile();
#endif // LOGFILE_H ///:~
The implementation
//: 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:[118]
//: C10:SingletonPattern2.cpp
// Meyers’ Singleton
#include
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 allows you to separate the "Singleton-ness" of an object from its implementation. This is achieved through templates, using the Curiously Recurring Template Pattern mentioned in Chapter 5.
//: C10:CuriousSingleton.cpp
// Separates a class from its Singleton-ness (almost)
#include
using namespace std;
template
class Singleton {
public:
static T& instance() {
static T theInstance;
return theInstance;
}
protected:
Singleton() {}
virtual ~Singleton() {}
private:
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};
// A sample class to be made into a Singleton
class MyClass : public Singleton
int x;
protected:
friend class Singleton
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
3. Deriving MyClass from Singleton