Implementing a Singleton in C++

Ravi Teja Narra
5 min readApr 1, 2021

No matter what level of proficiency someone has in C++ or any other programming language for that matter, when asked about design patterns, Singleton has got to be the first one that pops into the head. If you are reading this post, I am pretty sure you would have already used it at-least once in your career as a programmer.

On the other hand, there are many concerns on using it like it being an anti pattern, cannot be unit tested, and n number of other reasons. It is true that singleton is not the solution for every problem, but as a programmer it makes sense to implement your own singleton at-least once. Implementing a simple class like this is very straight forward, yet there are few subtle issues that can creep in during the process. So, in this post let’s try to implement our own singleton and see what issues we might have to solve along the way. The main intention for this post is not only to provide a code snippet of singleton to directly use, but also to bring to light common issues one might face while implementing any class in C++/

First things first, let’s implement the Singleton!

class Singleton {
public:
static Singleton * getInstance() {
if(nullptr == instance) {
instance = new Singleton();
}
return instance;
};
~Singleton() = default;
private:
static Singleton * instance;
Singleton() = default;
};
Singleton * Singleton::instance = nullptr;

In the above implementation there is one glaring issue, the instance object we are creating is never released. This is acceptable as we want the singleton object to live until the program terminates, and the OS will obviously clean up our mess! This is a well know singleton implementation and is called Trusty Leaky Singleton.

There are some other issues in the above implementation, what happens when we do something funky?

Singleton * ins1 = Singleton::getInstance();
Singleton ins2 = *ins1; - A
Singleton * ins3;
*ins3 = *ins1; - B

A will call the default copy constructor of the Singleton class. B will call the default assign operator of the class. Both of which are essentially creating a new object of Singleton. So, we got to remove access for Copy Constructor and Assign Operator.

class Singleton {
public:
static Singleton * getInstance() {
if(nullptr == instance) {
instance = new Singleton();
}
return instance;
};
~Singleton() = default;
private:
static Singleton * instance;
Singleton() = default;
Singleton(const Singleton&)= delete;
Singleton& operator=(const Singleton&)= delete;
};
Singleton * Singleton::instance = nullptr;

This looks pretty good. BUT… does this implementation still work if we are trying to use it in a multi threaded environment? You know the answer! NO. Well we just have to make sure to put the object creation in a critical section, this will make sure that multiple threads are not able to create the multiple objects.

#include <mutex>class Singleton {
public:
static Singleton * getInstance() {
std::lock_guard<std::mutex> lck(m_singletonMutex);
if(nullptr == m_pInstance) {
m_pInstance = new Singleton();
}
return m_pInstance;
};
~Singleton() = default;
private:
static Singleton * m_pInstance;
Singleton() = default;
Singleton(const Singleton&)= delete;
Singleton& operator=(const Singleton&)= delete;
std::mutex m_singletonMutex;
};
Singleton * Singleton::instance = nullptr;

This looks good, we made sure our implementation has multi-threaded support! But…. as always there goes another BUT! We gave our implementation multi-thread support, but at what cost? Here the cost is literally performance! We are acquiring a mutex lock every time we try to access the singleton object, which is clearly not optimal. Faced by this problem many of us would think, let’s do this clever optimisation!

#include <mutex>class Singleton {
public:
static Singleton * getInstance() {
if(nullptr == m_pInstance) {
std::lock_guard<std::mutex> lck(m_singletonMutex);
if(nullptr == m_pInstance) {
m_pInstance = new Singleton();
}
}
return m_pInstance;
};
~Singleton() = default;
private:
static Singleton * m_pInstance;
Singleton() = default;
Singleton(const Singleton&)= delete;
Singleton& operator=(const Singleton&)= delete;
std::mutex m_singletonMutex;
};
Singleton * Singleton::instance = nullptr;

On every call to getInstance() we first check if the object is created, if it is not , then we lock the mutex, then do the check again before creating the object. So, the mutex lock only happens when the object is created. This approach is called Double Checked Locking Optimisation (DCLO). It has its own name. So, one might think this it is bullet-proof. BUT…. here is the problem. The call m_pInstance = new Singleton() is executed in at-least the three following steps :

  1. Allocate Memory for Singleton.
  2. Create the Singleton object in memory.
  3. Let m_pInstance refer to Singleton.

There is no guarantee for order of execution of these 3 steps! for example due to optimisation the processor can execute the above steps in the order 1,3,2. In this situation it could so happen that, when one thread has entered the critical section and has executed the step 3 in the above order, another thread might just have called getInstance() and tried to perform some operation on the returned(still invalid) object. This will cause runtime error!

The solution for this problem is to make sure that we are using atomic operations while performing the assign operation. The code looks like follows:

#include <mutex>
#include <atomic>
class Singleton {
public:
static Singleton * getInstance() {
auto var = m_pInstance.load();
if(nullptr == var) {
std::lock_guard<std::mutex> lck(m_singletonMutex);
var = m_pInstance.load();
if(nullptr == var) {
var = new Singleton();
m_pInstance.store(var);
}
}
return m_pInstance;
};
~Singleton() = default;
private:
static Singleton * m_pInstance;
Singleton() = default;
Singleton(const Singleton&)= delete;
Singleton& operator=(const Singleton&)= delete;
static std::atomic<MySingleton*> m_pInstance;
std::mutex m_singletonMutex;
};
std::atomic<Singleton*> Singleton::m_pInstance;

Here we are implementing a logic similar to DCLO, but the atomic operation m_pInstance.store(var) is making sure that the implementation is thread safe! This looks kinda messy. But it is the cost one has to pay for using C++.

There is another simple implementation which is also thread safe.

class Singleton {
public:
static Singleton & getInstance() {
static Singleton instance;
return instance;
};
~Singleton() = default;
private:
static Singleton * instance;
Singleton() = default;
Singleton(const Singleton&)= delete;
Singleton& operator=(const Singleton&)= delete;
};

This is called Meyers Singleton implementation and leveraged this rule from the C++ standard. It basically says that scoped static variables are initialised only once.

--

--