Ensure a class has only one instance and provide a global point of access to it
The Singleton Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to it. This is useful when exactly one object is needed to coordinate actions across the system, such as a database connection, a configuration manager, or a logger.
In C++, a common way to implement a Singleton is by using a static member function that returns a reference or pointer to a static instance of the class. The constructor is typically made private or protected to prevent direct instantiation.
#include <iostream>
#include <string>
#include <mutex> // For thread-safe singleton
class Singleton {
private:
// Private constructor to prevent instantiation
Singleton(const std::string& value): value_(value) {
std::cout << "Singleton instance created with value: " << value_ << std::endl;
}
// Static member to hold the single instance
static Singleton* instance_;
std::string value_;
// For thread safety
static std::mutex mutex_;
public:
// Delete copy constructor and assignment operator
Singleton(const Singleton &other) = delete;
void operator=(const Singleton &) = delete;
// Static method to get the instance
static Singleton* GetInstance(const std::string& value);
std::string GetValue() const {
return value_;
}
void DoSomething() const {
std::cout << "Singleton (" << this << ") doing something with value: " << value_ << std::endl;
}
// Optional: A method to clean up the singleton instance
static void DestroyInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ != nullptr) {
delete instance_;
instance_ = nullptr;
std::cout << "Singleton instance destroyed." << std::endl;
}
}
};
// Initialize static members
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
// Implementation of GetInstance
Singleton* Singleton::GetInstance(const std::string& value) {
if (instance_ == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
instance_ = new Singleton(value);
}
}
return instance_;
}
// Example Usage in main:
int main() {
std::cout << "Attempting to get Singleton instance s1..." << std::endl;
Singleton* s1 = Singleton::GetInstance("INITIAL_VALUE");
std::cout << "s1 address: " << s1 << ", s1 value: " << s1->GetValue() << std::endl;
s1->DoSomething();
std::cout << "\nAttempting to get Singleton instance s2..." << std::endl;
Singleton* s2 = Singleton::GetInstance("NEW_VALUE (this value will be ignored if instance exists)");
std::cout << "s2 address: " << s2 << ", s2 value: " << s2->GetValue() << std::endl;
s2->DoSomething();
if (s1 == s2) {
std::cout << "\ns1 and s2 point to the same instance. Singleton pattern is working." << std::endl;
} else {
std::cout << "\nError: s1 and s2 point to different instances. Singleton pattern failed." << std::endl;
}
// Clean up the singleton instance before exiting
Singleton::DestroyInstance();
return 0;
}
// To compile and run (e.g., with g++):
// g++ your_file_name.cpp -o singleton_cpp -pthread
// ./singleton_cpp
# Singleton using __new__ method
class SingletonNew:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(SingletonNew, cls).__new__(cls)
print(f"SingletonNew instance created by __new__ with id: {id(cls._instance)}")
cls._instance._initialized_once = False
return cls._instance
def __init__(self, value="Default Value"):
if not self._initialized_once:
self.value = value
print(f"SingletonNew instance ({id(self)}) initialized by __init__ with value: '{self.value}'")
self._initialized_once = True
else:
print(f"SingletonNew instance ({id(self)}) __init__ called again, but not re-initializing value. Current value: '{self.value}'")
def get_value(self):
return self.value
def set_value(self, value):
self.value = value
print(f"SingletonNew instance ({id(self)}) value set to: '{self.value}'")
# Singleton using a Metaclass (more robust for controlling instantiation)
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
print(f"{cls.__name__} instance created by metaclass with id: {id(instance)}")
else:
print(f"{cls.__name__} instance ({id(cls._instances[cls])}) already exists, returning it.")
return cls._instances[cls]
class Logger(metaclass=SingletonMeta):
def __init__(self, file_name="log.txt"):
self.file_name = file_name
print(f"Logger instance ({id(self)}) initialized by __init__ with file: {self.file_name}")
def log(self, message):
print(f"Logger ({id(self)}) logging to {self.file_name}: {message}")
# Example usage for Python Singletons:
if __name__ == "__main__":
print("--- Singleton with __new__ ---")
s_new1 = SingletonNew("Initial Data")
print(f"s_new1 value: {s_new1.get_value()}, id: {id(s_new1)}")
s_new2 = SingletonNew("Second Call Data")
print(f"s_new2 value: {s_new2.get_value()}, id: {id(s_new2)}")
print(f"s_new1 is s_new2: {s_new1 is s_new2}")
s_new1.set_value("Updated Data from s_new1")
print(f"s_new2 value after s_new1 update: {s_new2.get_value()}")
print("\n--- Singleton with Metaclass (Logger) ---")
logger1 = Logger("app_log.txt")
logger1.log("This is the first log message from logger1.")
logger2 = Logger("another_log.txt")
logger2.log("This is a message from logger2 (should be same instance).")
print(f"logger1 is logger2: {logger1 is logger2}")
print(f"logger1 file: {logger1.file_name}, id: {id(logger1)}")
print(f"logger2 file: {logger2.file_name}, id: {id(logger2)}")
# To run:
# python your_file_name.py
The C++ main()
function in the "Implementation (C++)" section demonstrates the Singleton pattern. Here's a breakdown of what happens and the expected console output when compiled and run:
GetInstance
call:
Singleton* s1 = Singleton::GetInstance("INITIAL_VALUE");
GetInstance
method creates a new Singleton
object.s1
now points to this unique instance.GetInstance
call:
Singleton* s2 = Singleton::GetInstance("NEW_VALUE (this value will be ignored if instance exists)");
GetInstance
method is called again. This time, instance_
is not nullptr
.s1
points to) is returned. The constructor is not called again, and the "NEW_VALUE" argument is effectively ignored for instantiation purposes.s2
now points to the same unique instance as s1
.s1
and s2
. They should be identical.s1->GetValue()
and s2->GetValue()
will both be "INITIAL_VALUE" because they refer to the same object whose value_
was set upon its first creation.s1 == s2
evaluates to true, confirming they are the same instance.Singleton::DestroyInstance();
is called to deallocate the memory used by the singleton instance. This is important for avoiding memory leaks when using raw pointers.Note: [some_memory_address]
will be an actual memory address (e.g., 0x7ffee1c00580
) and will be the same for s1
and s2
.