The basic singleton
// Singleton.h
class Singleton
{
public:
static Singleton* Instance(){
if(!pInstance_)
pInstance_ = new Singleton;
return pInstance_;
}
private:
Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton();
static Singleton* pInstance_;
};
// Singleton.cpp
Singleton* Singleton::pInstance_ = 0;
The code above is the standard of Singleton we learned in school. However, such pattern is not safe in multithread programming. To address this problem, it is best to use synchronized:
class Singleton
{
public:
static synchronized Singleton* Instance(){
if(!pInstance_)
pInstance_ = new Singleton;
return pInstance_;
}
};
Dead reference problem and its solutions
// Singleton.h
class Singleton
{
public:
static Singleton* Instance(){
if(!pInstance_){
if(destroyed_)
OnDeadReference();
else
Create();
}
return pInstance_;
}
private:
static void Create(){
static Singleton theInstance;//Meyer's Singleton
pInstance_ = &theInstance;
}
static void OnDeadReference(){
throw std::runtime_error("Dead Reference Detected");
}
virtual ~Singleton(){
pInstance_ = 0;
destroyed_ = true;
}
static Singleton* pInstance_;
static bool destroyed_;
};
// Singleton.cpp
Singleton* Singleton::pInstance_ = 0;
bool Singleton::destroyed_ = false;
Although we successfully solved the undefined behavior problems, we still have to face the unsatisfactory problem. In this case, we would utilize the phoenix singleton pattern.
// Singleton.h
class Singleton
{
public:
static Singleton* Instance(){
if(!pInstance_){
if(destroyed_)
OnDeadReference();
else
Create();
}
return pInstance_;
}
private:
static void KillPhoenixSingleton();
static void Create(){
static Singleton theInstance;//Meyer's Singleton
pInstance_ = &theInstance;
}
virtual ~Singleton(){
pInstance_ = 0;
destroyed_ = true;
}
static Singleton* pInstance_;
static bool destroyed_;
};
void Singleton::OnDeadReference(){
Create();
new(pInstance_) Singleton;
atexit(KillPhoenixSingleton);
destroyed = false;
}
void Singleton::KillPhoenixSingleton(){
pInstance_->~Singleton();
}
// Singleton.cpp
Singleton* Singleton::pInstance_ = 0;
bool Singleton::destroyed_ = false;
In this case, the new operator does not allocate memory, it is called the placement new operator, which constructs objects at the address passed. Nevertheless, phoenix singleton pattern is not omnipotent, there is a solution that uses longevity to handle dead reference problem, which will not be discussed in this article
Singleton in multithreaded world
Singleton object is a shared global resource and all global resources are always suspect for race conditions and threading issues. In this case, we would use the double-checked locking pattern to solve this problem.
Singelton& Singleton::Instance(){
Lock guard(mutex_);
if(!pInstance_)
pInstance_ = new Singleton;
return *pInstance_;
}
However, this solution is inconvenient and lack of efficiency, because each call to Instance incurs locking and unlocking the synchronization object. The perfect solution is as follows:
Singelton& Singleton::Instance(){
if(!pInstance_){
Lock guard(mutex_);
if(!pInstance_)
pInstance_ = new Singleton;
}
return *pInstance_;
}