The Singleton Design Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. It is the most controversial Design Pattern because it is violating the “Single Responsibility Principle”.
🤔 Now you might be wondering: How does it violate the Single Responsibility Principle (SRP)?
Don’t worry I am going to explain….
As in the begin of this blog post I explained that singleton pattern ensures two things:
1. Single Instance:
It ensures that a class has just a single instance. The most common reason for this is to control access to some shared resource—for example, file system access, Global Configuration Management or Logging.
2. Global Access Point:
It provides a global access point for that single instance. This means the class is also responsible for providing access to its instance from anywhere in the program, effectively making the class a global service provider.
👉 The Singleton not only ensures that there is a single instance but also makes sure that instance can be accessed globally throughout the application.
This means the class is not just doing “one thing”, it is managing both its core behavior and its lifecycle. In other words, the Singleton pattern introduces two responsibilities into a single class. This violates the Single Responsibility Principle (SRP), which states that a module should have one reason to change — meaning it should have only one clearly defined responsibility.
When to use Singleton Method Design Pattern?
Here I am mentioning some scenarios where you should use singleton pattern.
1. Managing Shared Resources:
If a class which is having a shared resources required by many classes that in that case singleton pattern is useful. it ensures that only one instance of the class is created and used throughout the application.
This helps prevent issues like,
Resource Contention: Contention occurs when multiple components attempt to access a resource concurrently. A Singleton manages access to this resource, ensuring that only one instance is available, thereby avoiding conflicts.
Resource Consumption: Creating multiple instances of a resource-heavy class (like a database connection or file handler) can lead to excessive resource consumption. By employing a Singleton, you optimize resource usage, allowing for better performance and efficiency.
2. Resource-Intensive Objects:
If creation of an object is not efficient and it is expensive in that case singleton pattern is useful. Resource-intensive objects refer to objects in programming that require significant system resources like memory, CPU, or disk I/O to create, manage, or operate.
Examples:
- Database connection objects.
- Logging systems.
- Network/socket managers
3. Immutable object or Objects has no attributes:
If object is immutable or has no attributes, then use of singleton pattern beneficial for managing shared resources efficiently, ensuring consistency, and minimizing memory usage.
For Example:
Configuration settings where configuration settings are loaded once and remain unchanged throughout the application.
How to implement the Singleton:
Singleton class has only one instance. So, to implement the singleton pattern in C++, you need to ensure that a class has only one instance and provides a global access point to that instance.
Let’s see the steps to implement the Singleton pattern in C++:
Steps-1:
Make the constructor of the class private, to prevents the creation of new instances of the class from outside the class.
Steps-2:
Private constructor does not allow you to create even a single object. So, to solve this issue create a static method (commonly called getInstance()) that returns the instance of the class. But you should ensure that only one instance is created inside this method.
Steps-3:
You must also delete the copy constructor and assignment operator to avoid the creation of additional instances by copying.
Steps-4:
You should also take care the thread-safety in multithreaded environments. You can achieve this using either:
- Double-checked locking to guard the instance creation.
static Singleton* getInstance()
{
if (instance == nullptr)
{
lockGaurd();
if (instance == nullptr)
{
instance = new Singleton(); // Lazy initialization
}
}
return instance;
}
- Simply rely on a local static variable, which is guaranteed to be thread-safe since C++11.
// Thread-safe with local static variable
static Singleton& getInstance()
{
static Singleton instance;
return instance;
}
I believe, you might be wondering: Why is this thread-safe in C++11 and later?
Don’t worry, here is I am giving the answer.
Starting with C++11, the C++ Standard guarantees thread-safe initialization of function-local static variables.
This means that when you write:
static Singleton* getInstance()
{
static Singleton instance;
return instance;
}
Inside getInstance(), the C++ runtime ensures that:
- Only one thread will initialize the instance the very first time it’s accessed.
- If multiple threads call getInstance() simultaneously:
- All threads except one will wait during the initialization.
- Once initialization is complete, all threads will safely use the same shared instance.
There’s no need to write manual locking logic like std::mutex or double-checked locking. It is all handled automatically by the language.
👉 Lazy initialization is a design technique in which an object or resource is not created or initialized until it is actually needed.
Real-World Analogy
The President of a Country is an excellent example of the Singleton pattern.
- A country can have only one president at any given time, and everyone refers to this single entity to make important decisions.
- You can’t create more than one president simultaneously.
- Everyone in the country can connect with the president through a centralized point of communication, such as the presidential office or public addresses.
Implementing Singleton in C++:
Here we will implement a thread-safe singleton pattern. In this example, the Database class acts as a Singleton. It doesn’t have a public constructor, which means the only way to access its instance is through the getInstance() method. This method creates the object once, caches it, and returns the same instance on all subsequent calls — ensuring that only one Database object ever exists throughout the program’s lifecycle.
#include <mutex>
#include <string>
#include <iostream>
#include <thread>
// Singleton class to simulate a database connection
class Database
{
private:
// Static pointer to hold the single instance of the class
static Database* instance;
// Mutex to ensure thread safety during instance creation
static std::mutex mtx;
// Private constructor prevents instantiation from outside the class
Database()
{
std::cout << "Initializing database connection...\n";
}
public:
// Delete copy constructor to prevent copying of the singleton instance
Database(const Database&) = delete;
// Delete assignment operator to prevent copying of the singleton instance
Database& operator=(const Database&) = delete;
// Public method to provide global access to the single instance
static Database* getInstance()
{
// First check (not locked) for performance optimization
if (instance == nullptr)
{
// Locking to ensure only one thread creates the instance
std::lock_guard<std::mutex> lock(mtx);
// Second check (locked) to prevent race conditions (Double-checked locking)
if (instance == nullptr)
{
instance = new Database(); // Instance is created only once
}
}
return instance;
}
// Method to simulate execution of a SQL query
void query(const std::string& sql)
{
std::cout << "Executing query: " << sql << std::endl;
}
};
// Initialize static members
Database* Database::instance = nullptr;
std::mutex Database::mtx;
// Function to simulate database access from multiple threads
void threadFunction(const std::string& sql)
{
// Get the singleton instance and execute the query
Database* db = Database::getInstance();
db->query(sql);
}
int main()
{
// Start multiple threads to simulate concurrent database access
std::thread t1(threadFunction, "SELECT * FROM users\n");
std::thread t2(threadFunction, "INSERT INTO logs VALUES ('login')\n");
std::thread t3(threadFunction, "UPDATE settings SET value='dark' WHERE key='theme'\n");
// Wait for all threads to finish
t1.join();
t2.join();
t3.join();
return 0;
}
In the above code, the Database class implements the Singleton pattern to ensure that only one instance is created, even when accessed concurrently by multiple threads. A std::mutex is used to synchronize access during instance initialization, preventing race conditions and ensuring thread safety.
Pros and Cons of the Singleton Pattern:
Before deciding whether to use the Singleton pattern in your project, it’s important to understand its benefits and trade-offs. Below are the key pros and cons that will help you make an informed decision about where a Singleton fits and where it doesn’t.
âś… Pros:
1. Single Instance Guarantee:
Ensures that only one instance of the class exists throughout the program’s lifecycle.
2. Global Access Point:
Provides a globally accessible method (getInstance()) to access the singleton object, simplifying access across different parts of the application.
3. Lazy Initialization:
The singleton object is created only when it’s needed for the first time, saving resources if it’s never used.
4. Controlled Access to Shared Resource:
Useful when managing access to resources like configuration settings, loggers, or database connections where centralized control is important.
❌ Cons:
1. Violates the Single Responsibility Principle (SRP):
The Singleton pattern handles both the creation and global access responsibilities, making the class do more than one job.
2. Can Hide Poor Design Decisions:
Overusing singletons may lead to tightly coupled code, where components are too dependent on global state rather than being properly decoupled and modular.
3. Thread Safety Concerns:
In multithreaded environments, special care is needed to prevent multiple threads from creating separate instances simultaneously.
4. Difficult to Unit Test:
- Most testing frameworks rely on inheritance and dependency injection.
- Since the Singleton’s constructor is private and static methods can’t be overridden, it’s challenging to mock.
- This often forces developers to write workaround code or worse, skip writing tests altogether.
Relationship Between Singleton and Other Design Patterns:
Let us see relation of Singlton patterns with other patterns.
Facade:
A Facade class can often be turned into a Singleton, especially when a single shared interface to a subsystem is enough. Making it a Singleton ensures centralized control and avoids creating multiple redundant Facade instances.
Flyweight:
The Flyweight pattern may look similar to Singleton if all shared states are reduced to a single shared object. However, they differ in two keyways:
- Singleton ensures only one instance of a class exists, while Flyweight allows many instances, each with different intrinsic states.
- Singleton objects are often mutable, while Flyweight objects are typically immutable to allow safe sharing.
Abstract Factory, Builder, and Prototype:
These creational patterns can themselves be implemented as Singletons if there’s no need for multiple factory or builder instances. Doing so can simplify access and ensure consistent configuration across the application.