Singleton Pattern

Ensure a class has only one instance and provide a global point of access to it

Concept

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.

Purpose

Implementation (C++)

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

Code Example (Python)

# 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

Demonstration and Expected Output (C++)

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:

  1. First GetInstance call:
    • Singleton* s1 = Singleton::GetInstance("INITIAL_VALUE");
    • Since no instance exists, the GetInstance method creates a new Singleton object.
    • The private constructor is called, printing "Singleton instance created with value: INITIAL_VALUE".
    • s1 now points to this unique instance.
  2. Second GetInstance call:
    • Singleton* s2 = Singleton::GetInstance("NEW_VALUE (this value will be ignored if instance exists)");
    • The GetInstance method is called again. This time, instance_ is not nullptr.
    • The existing instance (the one 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.
  3. Verification:
    • The program prints the memory addresses of s1 and s2. They should be identical.
    • The values obtained via 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.
    • The condition s1 == s2 evaluates to true, confirming they are the same instance.
  4. Cleanup:
    • Singleton::DestroyInstance(); is called to deallocate the memory used by the singleton instance. This is important for avoiding memory leaks when using raw pointers.

Expected Console Output:

Attempting to get Singleton instance s1... Singleton instance created with value: INITIAL_VALUE s1 address: [some_memory_address], s1 value: INITIAL_VALUE Singleton ([some_memory_address]) doing something with value: INITIAL_VALUE Attempting to get Singleton instance s2... s2 address: [same_memory_address_as_s1], s2 value: INITIAL_VALUE Singleton ([same_memory_address_as_s1]) doing something with value: INITIAL_VALUE s1 and s2 point to the same instance. Singleton pattern is working. Singleton instance destroyed.

Note: [some_memory_address] will be an actual memory address (e.g., 0x7ffee1c00580) and will be the same for s1 and s2.