Singleton in C++: How Does It Work Under the Hood? Breakdown of a Legendary Question

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Singleton is Not Just "One Object"



If you've ever looked at Stack Overflow searching for an answer to the question: "How do you implement the Singleton design pattern?", you've seen thousands of votes, dozens of options, and heated debates in the comments. This question has become a classic because Singleton is one of the easiest patterns to understand but one of the most treacherous to implement correctly.



In this article, we won't just show you how to write a Singleton in C++. We'll look under the hood: we'll break down how static memory works, what initialization guarantees are, why a naive approach can kill performance, and how the final version (Meyers Singleton) became the de facto standard.



If you're preparing for an interview or want to understand C++ more deeply, this article is for you. Let's go!



What is a Singleton? A Quick Reminder



Singleton is a creational design pattern that guarantees a class has only one instance and provides a global point of access to it.



It seems simple, but in C++ there are nuances:

  • Thread safety during the first creation of the object.
  • Lifetime management (when is the object destroyed?).
  • Preventing copying and assignment.
  • Multithreading issues (data race).


Let's look at the evolution of the implementation — from naive to production-ready.



Naive Implementation: What's Wrong?



The simplest way is to make the constructor private and store a static pointer:



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

void doSomething() { // logic }

private: Singleton() {} // private constructor Singleton(const Singleton&) = delete; // prevent copying Singleton& operator=(const Singleton&) = delete; // prevent assignment

static Singleton* instance;};

// Definition of the static memberSingleton* Singleton::instance = nullptr;


Problems:

  1. Not thread-safe. If two threads call getInstance() simultaneously, both will see nullptr and create two objects. This violates the Singleton invariant.
  2. Memory leak. The object created with new is not automatically deleted. (Although in practice, memory is returned to the OS when the process terminates, the destructor is not called).
  3. Static initialization problem. If another static object tries to use the Singleton before it is initialized — UB.


You could add a mutex, but that would slow down every call to getInstance(), even after the object is created. Bad.



Double-Checked Locking (DCLP): Almost Correct, But Dangerous



A popular optimization is to check the pointer twice: first without locking, then with locking. This is called the Double-Checked Locking Pattern.



#include <mutex>

class Singleton {public: static Singleton* getInstance() { if (instance == nullptr) { // First check (without lock) std::lock_guard<std::mutex> lock(mutex); if (instance == nullptr) { // Second check (with lock) instance = new Singleton(); } } return instance; }

private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete;

static Singleton* instance; static std::mutex mutex;};

Singleton* Singleton::instance = nullptr;std::mutex Singleton::mutex;<

Blogs

Book Recommendations