Singleton Design pattern
Amit Nadiger
Polyglot(Rust??, Move, C++, C, Kotlin, Java) Blockchain, Polkadot, UTXO, Substrate, Sui, Aptos, Solana, Wasm, Proxy-wasm,AndroidTV, Dvb, STB, Linux, Cas, Engineering management.
The Singleton pattern is a design pattern that restricts the instantiation of a class to a single object. It ensures that only one instance of a class exists in the system and provides a global point of access to that instance. The Singleton pattern falls under the category of creational patterns, as it deals with object creation mechanisms.
Problem it solves?
There are situations where we need to have only one instance of a class throughout the lifetime of an application. For example, a logging class that records all events that occur in the application should only have one instance. If multiple instances of the logging class are created, it can cause problems such as conflicting log entries, loss of log data, and increased memory usage. The Singleton pattern solves this problem by ensuring that only one instance of the class is created and providing a global point of access to it.
How it works ?
The Singleton pattern is implemented by creating a class with a private constructor and a static method that returns the single instance of the class. The class maintains a private static variable that holds the single instance. The static method checks whether an instance of the class has been created or not. If an instance has not been created, it creates one and returns it. If an instance has already been created, it returns the existing instance.
Example in C++
Here is an example of a Singleton class in C++:
class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? void someMethod() {
? ? ? ? // implementation
? ? }
private:
? ? static Singleton* instance;
? ? Singleton() {}
};
Singleton* Singleton::instance = nullptr;
In this example, the Singleton class has a private constructor and a static method called getInstance() that returns the single instance of the class. The getInstance() method checks whether the instance variable is nullptr (i.e., whether an instance has been created or not). If an instance has not been created, it creates one and returns it. If an instance has already been created, it returns the existing instance. The class also has a private static variable called instance that holds the single instance.
Any problems with above Singletone class ?
Yes there are few problem, please try to guess!
No, the above implementation of the Singleton pattern is not thread-safe, and it can lead to several problems in a multi-threaded environment. One of the problems is that two or more threads can simultaneously access the getInstance() method when the instance variable is still nullptr, and they may each create a new instance of the Singleton class. This can result in multiple instances of the Singleton class, which violates the Singleton pattern's requirement of having only one instance of a class.
Another problem is that if one thread has created an instance of the Singleton class while another thread is in the process of creating a new instance, it may end up using an incomplete object that has not been fully initialized. This can result in undefined behavior and cause the program to crash or behave unpredictably.
The non-thread-safe singleton pattern can lead to several problems, including below:
To make the Singleton pattern thread-safe, we need to use some synchronization mechanisms to ensure that only one instance of the Singleton class is created and accessed by multiple threads. One common approach is to use the double-checked locking idiom, which involves checking the instance variable for nullptr inside a critical section and creating a new instance of the Singleton class if it is still nullptr. This ensures that only one thread can create a new instance of the Singleton class, and all subsequent calls to the getInstance() method will return the same instance.
Here's an example of a thread-safe Singleton implementation using the double-checked locking idiom:
class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? std::lock_guard<std::mutex> lock(mutex_);
? ? ? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? ? ? instance = new Singleton();
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? void someMethod() {
? ? ? ? // implementation
? ? }
private:
? ? static Singleton* instance;
? ? static std::mutex mutex_;
? ? Singleton() {}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
In the above implementation, we use a std::mutex object to ensure that only one thread can access the critical section where we create a new instance of the Singleton class. The lock_guard object ensures that the mutex is released automatically when the function returns, regardless of whether an exception is thrown or not.
By using a thread-safe Singleton implementation, we can avoid the problems of multiple instances and undefined behavior that can arise in a non-thread-safe implementation.
Is now above singletone class is really Singletone in all scenarios? Is it safe? Can we can ensure single instance in every case?
No, still we can copy the instance returned from getInstance() method to another variable!
2. How to ensure singletone instance by restricting copy of single ton instance:
To ensure that only one instance of the Singleton class is created, it is necessary to prevent copying of the Singleton object. There are two common approaches to achieving this:
Here is an example of how this can be done:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? // Lock the mutex
? ? ? ? lock_guard<mutex> lock(mutex_);
? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? void someMethod() {
? ? ? ? // implementation
? ? }
private:
? ? static Singleton* instance;
? ? static mutex mutex_; // declare mutex
? ? Singleton() {}
? ? // Private copy constructor and assignment operator
? ? Singleton(const Singleton&);
? ? Singleton& operator=(const Singleton&);
};
Singleton* Singleton::instance = nullptr;
mutex Singleton::mutex_; // define mutex
void testSingleton() {
? ? Singleton* instance = Singleton::getInstance();
? ? instance->someMethod();
}
void threadFunction() {
? ? Singleton* instance = Singleton::getInstance();
? ? cout << "Instance address from thread " << this_thread::get_id() << ": " << instance << endl;
}
int main() {
? ? // Create multiple threads to access Singleton
? ? thread t1(testSingleton);
? ? thread t2(testSingleton);
? ? thread t3(testSingleton);
Singleton* instance1 = Singleton::getInstance();
? ? Singleton* instance2 = Singleton::getInstance();
? ? cout << "Instance 1 address: " << instance1 << endl;
? ? cout << "Instance 2 address: " << instance2 << endl;
// Compilation error for below 2 lines which invloke copy constructor
// Error : Singleton::Singleton(const Singleton&)’ is private within this context
// Singleton instance4 = *instance1;?
// Singleton instance4(*instance1);
? ? thread t11(threadFunction);
? ? thread t12(threadFunction);
? ? thread t13(threadFunction);
? ? t1.join();
? ? t2.join();
? ? t3.join();
? ? t11.join();
? ? t12.join();
? ? t13.join();
? ? return 0;
}
/*
Op =>
amit@DESKTOP-9LTOFUP:~/OmPracticeC++$ ./a.out
Instance 1 address: 0x7fa358000b70
Instance 2 address: 0x7fa358000b70
Instance address from thread 140339524273728: 0x7fa358000b70
Instance address from thread 140339515819584: 0x7fa358000b70
Instance address from thread 140339507365440: 0x7fa358000b70
*/
2. Delete the copy constructor and assignment operator: In C++11 and later, the copy constructor and assignment operator can be deleted using the delete keyword. This prevents any attempt to copy the object at compile-time.
Here is an example of how this can be done :
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? // Lock the mutex
? ? ? ? lock_guard<mutex> lock(mutex_);
? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? void someMethod() {
? ? ? ? // implementation
? ? }
private:
? ? static Singleton* instance;
? ? static mutex mutex_; // declare mutex
? ? Singleton() {}
? ? // Delete copy constructor and assignment operator
? ? Singleton(const Singleton&) = delete;
? ? Singleton& operator=(const Singleton&) = delete;
};
Singleton* Singleton::instance = nullptr;
mutex Singleton::mutex_; // define mutex
void testSingleton() {
? ? Singleton* instance = Singleton::getInstance();
? ? instance->someMethod();
}
void threadFunction() {
? ? Singleton* instance = Singleton::getInstance();
? ? cout << "Instance address from thread " << this_thread::get_id() << ": " << instance << endl;
}
int main() {
? ? // Create multiple threads to access Singleton
? ? thread t1(testSingleton);
? ? thread t2(testSingleton);
? ? thread t3(testSingleton);
Singleton* instance1 = Singleton::getInstance();
? ? Singleton* instance2 = Singleton::getInstance();
? ? cout << "Instance 1 address: " << instance1 << endl;
? ? cout << "Instance 2 address: " << instance2 << endl;
// Compilation error for below 2 lines which invloke copy constructor
// Error :? use of deleted function ‘Singleton::Singleton(const Singleton&)’
// Singleton instance4 = *instance1;?
// Singleton instance4(*instance1);
? ? thread t11(threadFunction);
? ? thread t12(threadFunction);
? ? thread t13(threadFunction);
? ? t1.join();
? ? t2.join();
? ? t3.join();
? ? t11.join();
? ? t12.join();
? ? t13.join();
? ? return 0;
}
/*
Op =>
amit@DESKTOP-9LTOFUP:~/OmPracticeC++$ ./a.out
Instance 1 address: 0x7fa358000b70
Instance 2 address: 0x7fa358000b70
Instance address from thread 140339524273728: 0x7fa358000b70
Instance address from thread 140339515819584: 0x7fa358000b70
Instance address from thread 140339507365440: 0x7fa358000b70
*/
Both of the above approaches ensure that the Singleton class cannot be copied or assigned to, which is the desired behavior to maintain the Singleton pattern.
The second approach of using the =delete syntax explicitly deletes the copy constructor and assignment operator, preventing both external code and internal code from calling these methods. This approach provides a more explicit and foolproof solution for enforcing the Singleton pattern and I think is more easy to understand .
Therefore, the second approach of deleting the copy constructor and assignment operator is generally considered to be the better approach for enforcing the Singleton pattern.
In Kotlin, you can create a Singleton using the object keyword. Here's an example:
object Singleton {
? ? init {
? ? ? ? // Initialization code here
? ? }
? ? fun someMethod() {
? ? ? ? // Implementation code here
? ? }
}
In Kotlin, you can create a Singleton using the object keyword. Here's an example:
You can access the Singleton object in your code like this:
Singleton.someMethod()
This approach guarantees that only one instance of the Singleton will be created, and it is thread-safe by default.
Advantages
The Singleton pattern has several advantages, including:
Disadvantages
The Singleton pattern also has some disadvantages, including:
Manually restricted single instance can be preferred over a Singleton in some cases, especially when it comes to testing. This is because a manually restricted single instance allows for more flexibility and control when creating and managing instances of a class.
With a Singleton, there is typically only one instance of the class available throughout the entire application, which can make it difficult to test certain scenarios that require multiple instances or different configurations of the class.
On the other hand, with a manually restricted single instance, you can create and manage instances of the class as needed, allowing for more flexibility in testing and development. For example, you can create new instances with different configurations or mock instances for testing purposes.
Overall, whether to use a Singleton or a manually restricted single instance depends on the specific requirements and constraints of the application, as well as the preferences and needs of the development team.
Application
The Singleton pattern is used in situations where there is a need for a single instance of a class throughout the lifetime of an application. Some common use cases for Singleton pattern are:
Overall, the Singleton pattern is useful in situations where there is a need to restrict the instantiation of a class to a single object and provide a global point of access to it.
Thanks for reading till end , Please comment if you have any questions or suggestions.