Technology Sharing

Design and implementation of singleton pattern in C: from basic to advanced

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Design and implementation of singleton pattern in C++: from basic to advanced

Title: In-depth understanding of the singleton pattern in C++: design, implementation and application

In software development, design patterns are proven solutions to specific problems. They help developers build software in a more consistent, reusable, and maintainable way. The Singleton Pattern is one of the most basic and widely used design patterns. It ensures that a class has only one instance and provides a global access point to obtain this instance. This article will take a deep look at how to implement the Singleton Pattern in C++, from the basic implementation to the thread-safe version, to modern C++ features such as smart pointers andstd::call_once) application, striving to provide readers with a comprehensive and practical guide.

1. Basic concepts of singleton pattern

The core idea of ​​the singleton pattern is to ensure that a class has only one instance and provide a global access point. This pattern is very useful when you need to control resource access (such as configuration file readers, loggers, database connection pools, etc.). The key to the singleton pattern is:

  • The class's constructor is private, preventing external code from instantiating the object directly.
  • A static instance is provided within the class and returned when needed.
  • Provide a static public access method, usuallygetInstance(), used to obtain the only instance of a class.
2. Basic Implementation

First, let's look at a simple singleton pattern implementation without considering thread safety issues:

#include <iostream>

class Singleton {
private:
    // 私有构造函数,防止外部实例化
    Singleton() {}

    // 私有拷贝构造函数和赋值操作符,防止拷贝
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态实例
    static Singleton* instance;

public:
    // 静态方法,返回类的唯一实例
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    // 示例方法
    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    // 析构函数(通常是protected或public,取决于是否需要外部delete)
    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }
};

// 初始化静态实例
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    if (s1 == s2) {
        std::cout << "s1 and s2 are the same instance." << std::endl;
    }

    s1->doSomething();
    // 注意:在多线程环境下,上述实现可能存在安全问题

    // 通常不推荐手动删除单例对象,除非有特别理由
    // delete Singleton::instance; // 谨慎使用

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
3. Thread-safe implementation

In a multithreaded environment, the above implementation may cause multiple threads to entergetInstance()method and create instances multiple times. To solve this problem, we can use a mutex lock (such asstd::mutex) to ensure thread safety:

#include <mutex>
#include <iostream>

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance;
    static std::mutex mtx;

public:
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }
};

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

// main函数保持不变
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
4. Use smart pointers to manage singleton life cycle

In order to automatically manage the life cycle of a singleton object (that is, automatically destroy it at the end of the program), we can use a smart pointer (such asstd::unique_ptr) instead of raw pointers:

#include <memory>
#include <mutex>
#include <iostream>

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr<Singleton> instance;
    static std::mutex mtx;

public:
    static Singleton& getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (!instance) {
            instance = std::make_unique<Singleton>();
        }
        return *instance;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    // 析构函数被智能指针管理,无需手动调用
    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }

    // 禁止拷贝和移动
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
};

std::unique_ptr<Singleton> Singleton::instance = nullptr;
std::mutex Singleton::mtx;

int main() {
    // 注意这里返回的是引用,无需使用指针
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();

    if (&s1 == &s2) {
        std::cout << "s1 and s2 are the same instance." << std::endl;
    }

    s1.doSomething();

    // 程序结束时,智能指针会自动销毁Singleton实例

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
5. Usestd::call_onceoptimization

Starting from C++11,std::call_onceIt provides a more efficient and concise way to ensure that a function is called only once, even in a multi-threaded environment. We can use this feature to further optimize the implementation of the singleton pattern:

#include <memory>
#include <mutex>
#include <iostream>
#include <once.h> // 注意:实际上应使用#include <mutex>中的std::call_once

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr<Singleton> instance;
    static std::once_flag onceFlag;

    static void createInstance() {
        instance = std::make_unique<Singleton>();
    }

public:
    static Singleton& getInstance() {
        std::call_once(onceFlag, createInstance);
        return *instance;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    // 析构函数和移动操作符的禁用同上
};

std::unique_ptr<Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::onceFlag;

// main函数保持不变
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

NOTE: In the above code, I incorrectly quoted#include <once.h>, you should actually use<mutex>In the header filestd::once_flagandstd::call_once

VI. Conclusion

The singleton pattern is a very useful design pattern in C++, which ensures that a class has only one instance and provides a global access point. However, you need to pay attention to thread safety issues when implementing the singleton pattern, especially in a multi-threaded environment. By using mutex locks, smart pointers, andstd::call_onceWith modern C++ features such as __C__ , we can implement the singleton pattern more safely and efficiently.

When designing the singleton pattern, you also need to consider some additional factors, such as the lifecycle management of the singleton object (whether it needs to be automatically destroyed at the end of the program), whether lazy loading is allowed (that is, delaying the creation of the instance until the first use), etc. In addition, in some cases, you may need to consider variations of the singleton pattern, such as the multiple-case pattern (controlling the number of class instances, but not exceeding a certain upper limit) or the context-based singleton pattern (returning different instances based on different contexts).

I hope this article can help readers better understand the singleton pattern in C++ and apply it flexibly in actual projects.