Understanding Singleton Design pattern

Understanding Singleton Design pattern

·

4 min read

Introduction

Design patterns are fundamental in software development, providing time-tested solutions to common problems. Among these patterns, the Singleton design pattern is particularly noteworthy. It ensures that a class has only one instance and provides a global point of access to it. This blog will delve into the Singleton pattern, explaining its significance, implementation, and use cases.

Why Singleton?

The Singleton pattern is useful in scenarios where exactly one instance of a class is needed to coordinate actions across the system. Examples include:

  • Configuration Management

    • Manage application-wide configuration settings that need global access.

    • Ensure consistent settings across the entire application.

    • Load settings from files, environment variables, or remote services at startup.

  • Logging

    • Provide a single logging mechanism throughout the application.

    • Ensure all parts of the application log messages consistently.

    • Avoid creating multiple instances of the logger, reducing ambiguity.

  • Database Connections

    • Manage a single database connection pool.

    • Control database access and resource management.

    • Ensure efficient use of database connections by preventing multiple connections to the same database.

The Singleton pattern follows Lazy initialisation that means the instance is only created when its needed.

Implementation in C++

The Singleton design pattern ensures that a class has only one instance and provides a global point of access to it. Here's a step-by-step guide to implementing the Singleton pattern in C++:

Step 1: Private Constructor

The constructor of the class is made private to prevent instantiation from outside the class. This ensures that objects of the class cannot be created directly using the new keyword or by calling the constructor.

  • By making the constructor private, we prevent external code from creating new instances of the class. This is essential for maintaining a single instance of the class.

  • Only the class itself can access the private constructor, allowing control over instance creation.

class Singleton {
private:
    // Private constructor to prevent instantiation
    Singleton() {}

public:
    // Deleted copy constructor and assignment operator to prevent copying
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton& obj) = delete;
};

Step 2: Static Pointer to Instance

A static pointer is declared to hold the single instance of the class. This pointer is initialized to nullptr and is used to check if an instance of the class already exists.

  • The static pointer instance is a member of the class that holds the address of the single instance.

  • Being static means it is shared among all instances of the class and exists for the lifetime of the program. This ensures that only one instance of the class is created and managed.

class Singleton {
private:
    // Static pointer to hold the single instance of the class
    static Singleton* instance;
    Singleton() {}

public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

The static pointer should be initialized outside the class to nullptr . This serves as a check to ensure that the Singleton instance is not created yet and we need to create one now. (lazy initialisation)

Step 3: Public Static Method

A public static method, typically named getInstance, provides global access to the single instance of the class. This method checks if the instance already exists; if not, it creates the instance and returns its pointer.

  • The getInstance method checks if instance is nullptr. If it is, it creates a new instance of the class and assigns it to instance.

  • If instance is not nullptr, it simply returns the existing instance.

  • This method ensures that only one instance of the class is created and provides global access to it.

class Singleton {
    private:

    static Singleton* instance;
    Singleton() {}

    public:

    Singleton (Singleton& obj) = delete;
    void operator=(Singleton& obj) = delete;

    static Singleton* getInstance() {
        if(instance == nullptr) {
            instance = new Singleton();
            return instance;
        }
        return instance;
    }

Putting everything together

Singleton.cpp

#ifndef SINGLETON_H
#define SINGLETON_H

#include <string>

class Singleton {
    private:

    std::string value;
    static Singleton* instance;
    Singleton() {} //private constructor so the objects cannot be created

    public:

    //deleting the copy constructor and assignment operator
    Singleton (Singleton& obj) = delete;
    void operator=(Singleton& obj) = delete;

    static Singleton* getInstance() {
        if(instance == nullptr) {
            instance = new Singleton();
            return instance;
        }
        return instance;
    }

    void setValue(std::string value) {
        this->value = value;
    }

    std::string getValue() {
        return this->value;
    }

};

Singleton* Singleton::instance = nullptr;

#endif

main.cpp

#include<iostream>
#include "Singleton.cpp"

int main() {
    Singleton* singleton1 = Singleton::getInstance();
    Singleton* singleton2 = Singleton::getInstance();

    singleton1->setValue("Abhav");
    std::cout<<singleton1->getValue()<<std::endl;

    singleton2->setValue("Goel") ;
    std::cout<<singleton2->getValue()<<std::endl;

    std::cout<<"Printing Address of instances"<<std::endl;
    std::cout<<"singleton1's address: "<<singleton1<<std::endl;
    std::cout<<"singleton2's address: "<<singleton2<<std::endl;

}

Now if you call the getInstance() function, it will always return the same reference to the first initialised instance variable.

Output