[C++ design pattern--Singleton pattern]

Singleton pattern

What is singleton pattern

The singleton pattern is a design pattern that ensures that a class has only one instance and provides a global access point so that various parts of the program can share this instance. The main purpose of the singleton pattern is to limit the instantiation process of a class, ensure that only one instance can exist at runtime, and provide a global access method.
In the singleton pattern, it is common to declare a class's constructor as private so that external code cannot instantiate the class directly. Then, control the creation and access of the instance through static member functions or static member variables. If the instance does not exist, create a new instance; if the instance already exists, return the existing instance.

Characteristics of singleton pattern

1. Private constructor: The constructor of a singleton class is usually declared as private to prevent external direct creation of instances.
2. Static member variables or static member functions: Save unique instances through static member variables, or provide methods of accessing instances through static member functions.
3. Global access point: The singleton mode provides a global access point, allowing other parts of the program to access the singleton instance through a specific interface.

Why use singleton pattern

Global access point: The singleton pattern provides a global access point so that various parts of the program can easily access the same instance. This helps to uniformly manage resources or status.
Resource sharing: When an instance of a class needs to be shared by multiple parts, using the singleton mode can avoid repeatedly creating instances and save system resources.
Configuration management: The singleton mode is often used to manage the configuration information of the system to ensure that there is only one configuration instance during the entire program running and can be accessed by all modules.
The situation of managed objects such as thread pools and connection pools: When you need to limit the number of certain resources in the system, you can use the singleton mode to manage instances of these resources.
Logging: The singleton mode can be used to record log information in the system, ensuring that there is only one log instance, and providing a global access point to facilitate recording the running status of the system.
Avoid repeatedly creating expensive objects: When the creation and destruction of objects has a large overhead, using the singleton mode can reduce the number of creations and improve performance.
Although the singleton pattern has its uses, it needs to be used with caution because it may lead to the existence of global state and increase the complexity of the code. In some cases, dependency injection, factory pattern, etc. are also alternatives that can be considered.

Disadvantages of singleton pattern

Although the singleton pattern is useful in certain situations, it also has some disadvantages that need to be considered when using it:
Global state: The singleton pattern introduces global state, which can lead to increased complexity of the code. Because any part can access the singleton instance, it can lead to dependencies that are difficult to track and understand.
Hidden dependencies: Using the singleton pattern may hide dependencies between classes because the singleton instance can be accessed from anywhere. This increases the coupling of the code and reduces flexibility.
Difficulty in testing: The singleton pattern can make the code difficult to test. Because singleton instances are globally accessible, it is difficult to use mock objects in place of actual singleton instances in tests.
Violation of the Single Responsibility Principle: Singleton objects are usually responsible for their own creation, management, and business logic, which may lead to a violation of the Single Responsibility Principle. This makes the code difficult to maintain and extend.
May cause concurrency problems: In a multi-threaded environment, the lazy singleton mode may cause race condition problems, which require special handling to ensure thread safety.
Blocking scalability: The way the singleton pattern is implemented may block program scalability. For example, if you need to switch from singleton mode to multiple instance mode or other design patterns, you may need to modify a lot of code.

Singleton pattern implementation

Lazy Initialization method (unsafe)

Implement a simple lazy singleton pattern. The constructor is declared private to ensure that external instances cannot be created directly. Save the unique instance through the static member variable instance, and create it lazily in the getInstance method. The sample code is as follows:

#include <iostream>

class Singleton {
    
    
public:
    // 获取单例实例的静态方法
    static Singleton& getInstance() {
    
    
        // 使用懒汉式(Lazy Initialization)方式创建单例对象
        // 如果实例不存在,则创建一个新的实例,否则返回现有实例
        if (instance == nullptr) {
    
    
            instance = new Singleton();
        }
        return *instance;
    }

    // 其他成员函数

private:
    // 将构造函数、拷贝构造函数和赋值运算符声明为私有,防止外部创建多个实例
    Singleton() {
    
    
        // 构造函数私有,防止外部创建实例
        std::cout << "Singleton instance created." << std::endl;
    }

    Singleton(const Singleton&) = delete; // 禁用拷贝构造函数
    Singleton& operator=(const Singleton&) = delete; // 禁用赋值运算符

    // 静态成员变量,用于保存唯一实例
    static Singleton* instance;
};

// 静态成员变量初始化为nullptr
Singleton* Singleton::instance = nullptr;

int main() {
    
    
    // 获取单例实例
    Singleton& singleton1 = Singleton::getInstance();
    Singleton& singleton2 = Singleton::getInstance();

    // 输出地址,确保两次获取的是同一个实例
    std::cout << "Singleton 1 address: " << &singleton1 << std::endl;
    std::cout << "Singleton 2 address: " << &singleton2 << std::endl;

    return 0;
}

It should be noted that the lazy singleton mode may cause race conditions in a multi-threaded environment. Because multiple threads may detect that the instance is nullptr at the same time, and then try to create a new instance. To ensure thread safety, different thread safety mechanisms can be used to fix this problem.

Double-Checked Locking (Thread-Safe)

In order to ensure thread safety, you can use std::mutex to ensure that only one thread can enter the critical section when creating an instance, thus avoiding race condition problems. This method is called double-checked locking (Double-Checked Locking) mechanism. It is relatively efficient in implementation, but it also needs to be used with care to ensure that it can work correctly under C++11 and above standards. The sample code is as follows:

#include <iostream>
#include <mutex>

class Singleton {
    
    
public:
    // 获取单例实例的静态方法
    static Singleton& getInstance() {
    
    
        // 使用双重检查锁(Double-Checked Locking)确保线程安全
        if (instance == nullptr) {
    
    
            std::lock_guard<std::mutex> lock(mutex);  // 使用互斥锁保护实例的创建过程
            if (instance == nullptr) {
    
    
                instance = new Singleton();
            }
        }
        return *instance;
    }

    // 其他成员函数

private:
    Singleton() {
    
    
        std::cout << "Singleton instance created." << std::endl;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员变量,用于保存唯一实例
    static Singleton* instance;
    static std::mutex mutex;  // 互斥锁,确保线程安全
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

int main() {
    
    
    // 获取单例实例
    Singleton& singleton1 = Singleton::getInstance();
    Singleton& singleton2 = Singleton::getInstance();

    // 输出地址,确保两次获取的是同一个实例
    std::cout << "Singleton 1 address: " << &singleton1 << std::endl;
    std::cout << "Singleton 2 address: " << &singleton2 << std::endl;

    return 0;
}

Local static variables (thread safe)

In addition to using double-checked locking mechanisms, there are other thread-safe singleton pattern implementations. The following is one of them, using local static variables. The sample code is as follows:

#include <iostream>

class Singleton {
    
    
public:
    // 获取单例实例的静态方法
    static Singleton& getInstance() {
    
    
        static Singleton instance;  // 局部静态变量,确保线程安全
        return instance;
    }

    // 其他成员函数

private:
    Singleton() {
    
    
        std::cout << "Singleton instance created." << std::endl;
    }

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

int main() {
    
    
    // 获取单例实例
    Singleton& singleton1 = Singleton::getInstance();
    Singleton& singleton2 = Singleton::getInstance();

    // 输出地址,确保两次获取的是同一个实例
    std::cout << "Singleton 1 address: " << &singleton1 << std::endl;
    std::cout << "Singleton 2 address: " << &singleton2 << std::endl;

    return 0;
}

In this implementation, declare the instance as a static local variable so that it is only initialized the first time getInstance is called. C++11 and above standards guarantee the thread safety of local static variables because the initialization process is mutually exclusive in a multi-threaded environment. The advantage of this approach is that it is simple and thread-safe and does not require explicit use of a mutex.
It should be noted that the limitation of this approach is that the creation timing of the singleton cannot be controlled at runtime, because it is automatically created when getInstance is called for the first time. If you need to create a singleton when the program starts, or if you need to delay creation until a specific moment in the program, you need to consider other implementation methods.

Created via std::call_once (C++11 thread-safe)

#include <iostream>

class Singleton {
    
    
public:
    // 获取单例实例的静态方法
    static Singleton& getInstance() {
    
    
        std::call_once(initFlag, &Singleton::initInstance);
        return *instance;
    }

    // 其他成员函数

private:
    Singleton() {
    
    
        std::cout << "Singleton instance created." << std::endl;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 初始化单例实例的函数
    static void initInstance() {
    
    
        instance = new Singleton();
    }

    // 静态成员变量,用于保存唯一实例
    static Singleton* instance;
    static std::once_flag initFlag;  // 标记是否已经初始化过
};

Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

int main() {
    
    
    // 获取单例实例
    Singleton& singleton1 = Singleton::getInstance();
    Singleton& singleton2 = Singleton::getInstance();

    // 输出地址,确保两次获取的是同一个实例
    std::cout << "Singleton 1 address: " << &singleton1 << std::endl;
    std::cout << "Singleton 2 address: " << &singleton2 << std::endl;

    return 0;
}

In the above code, std::once_flag is used to mark whether initialization has been completed, and std::call_once ensures that the initInstance function will only be executed once when getInstance is called for the first time. This ensures thread-safe creation of singleton instances while avoiding the complexity of manually managing locks.

Guess you like

Origin blog.csdn.net/a1379292747/article/details/135264703