The Singleton is one of the first design patterns most engineers learn — and one of the most misused. The idea is simple: guarantee that a class has exactly one instance for the lifetime of the program, and provide a single, well-known point of access to it. Database connections, logging systems, and configuration managers are the textbook use cases, since having more than one of them running around your application usually means wasted resources or, worse, inconsistent state.
One instance. One access point. No way around it.
The Basic Implementation
Here’s a minimal Singleton for a DatabaseConnection class. The constructor is private, so nothing outside the class can call new on it directly. The only way in is through a static getInstance() method:
// main.cpp
#include <iostream>
using namespace std;
class DatabaseConnection {
/*
We make the constructor private to make sure
that nothing, which is part of application can use
new keyword to create object.
*/
private:
static DatabaseConnection* instance;
DatabaseConnection(){
}
/*
We need a public static method to get the instance of this class
to make sure that client does not have to create an object. If we
don't make it a static method clients have to create an object which
is not possible here.
*/
public:
static DatabaseConnection* getInstance(){
if(instance == NULL){
instance = new DatabaseConnection();
}
return instance;
}
};
DatabaseConnection* DatabaseConnection::instance = NULL;
int main(){
DatabaseConnection* db = DatabaseConnection::getInstance();
DatabaseConnection* db2 = DatabaseConnection::getInstance();
cout << db << endl;
cout << db2 << endl;
return 0;
}
Run this and db and db2 print the exact same address. Both variables point at the exact same object — that’s the whole pattern working as intended.
Three Key Pillars of Singleton:
- Private constructor: Prevents external instantiation.
- Private static pointer: Holds the single instance, initialized to
NULLoutside the class. - Public static method: Creates the instance on first call and returns the existing instance on subsequent calls.
The Trap: This Isn’t Thread-Safe
The implementation above works perfectly in a single-threaded program. The moment you introduce concurrency, it breaks. Look closely at getInstance():
if (instance == NULL) {
instance = new DatabaseConnection();
}
return instance;
If two threads call getInstance() at nearly the same moment, both can read instance as NULL before either one finishes constructing it. Result: two separate DatabaseConnection objects get created, silently defeating the entire point of the pattern. Depending on what the class does in its constructor, you could end up with duplicate connections, race conditions, or a leaked object nobody has a reference to.
This is exactly the kind of bug that hides in production for months. It won’t show up in a quick manual test; it shows up under real concurrent load, which is precisely when you can least afford it.
Fixing It: Meyers’ Singleton
The cleanest fix in modern C++ (C++11 and later) doesn’t need a mutex or a raw pointer at all. It’s called the Meyers’ Singleton, named after Scott Meyers, and it leans on a guarantee the language itself provides: a function-local static variable is initialized exactly once, and the C++11 standard mandates that this initialization is thread-safe.
// main.cpp - Thread-safe Meyers' Singleton
class DatabaseConnection {
private:
DatabaseConnection() {}
public:
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
static DatabaseConnection& getInstance(){
static DatabaseConnection instance;
return instance;
}
};
int main(){
DatabaseConnection& db = DatabaseConnection::getInstance();
DatabaseConnection& db2 = DatabaseConnection::getInstance();
// &db == &db2, guaranteed, and safe under concurrent first calls.
}
Two Critical Improvements:
- Local static object: The instance is now a local static object rather than a heap-allocated pointer. No manual
new, no memory leaks, and no null check needed. - Deleted Copy & Assignment: The copy constructor and assignment operator are explicitly deleted (
= delete), preventing callers from copying the Singleton instance.
This version is shorter, safer, and idiomatic in any C++11-or-later codebase.
When (Not) to Reach for Singleton
It’s worth saying plainly: the Singleton pattern gets overused. It’s effectively global state with a design-pattern name attached, and global state makes code harder to test — you can’t easily swap in a mock for unit tests, and hidden dependencies between unrelated parts of the codebase creep in silently.
Before reaching for it, ask whether dependency injection would solve the same problem more explicitly by passing the shared object in rather than having every caller reach out and grab it globally.
That said, for genuinely single-instance resources — a connection pool, a hardware interface, an application-wide logger — Singleton is still the right tool. Just reach for the Meyers’ version!
