首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

设计模式-单例模式

  • 25-02-19 08:20
  • 4758
  • 5574
blog.csdn.net

系列文章目录

总目录链接


文章目录

  • 系列文章目录
    • 总目录链接
  • 设计模式-单例模式
    • Overview
    • 1.单例模式中懒汉式和饿汉式区别
      • 1.1.懒汉式(Lazy Initialization)
      • 1.2.饿汉式(Eager Initialization)
      • 1.3.选择建议
    • 2.单例模式 Singleton 实现方式及详细介绍
      • 2.1. 懒汉式(线程不安全)
      • 2.2. 懒汉式(线程安全)
      • 2.3. 饿汉式(线程安全)
      • 2.4. C++11后 局部静态变量(线程安全)
      • 2.5. 注意事项
    • 3.单例模式的应用场景
    • 4.单例模式在多线程环境下如何保证线程安全?
      • 4.1. 双重检查锁定(Double-Checked Locking, DCL)
      • 4.2. 局部静态变量
      • 4.3. 原子操作
      • 4.4. 使用 `std::call_once` 和 `std::once_flag`
      • 4.5. 注意事项
    • 5.除了懒汉式和饿汉式,还有哪些实现单例模式的方法?
      • 5.1. 线程安全静态局部变量(C++11)
      • 5.2. 基于函数的局部静态变量
      • 5.3. 使用 `std::call_once` 和 `std::once_flag`
      • 5.4. 使用智能指针
      • 5.5. 注意事项
    • 关于作者


设计模式-单例模式

Overview

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。


初次接触,直接说懒汉式和饿汉式,容易把人搞懵掉,先认识一下吧!

1.单例模式中懒汉式和饿汉式区别

单例模式中的懒汉式和饿汉式是两种不同的实现策略,它们在实例化单例对象的时机和线程安全性方面有所不同。以下是懒汉式和饿汉式单例模式的主要区别:

1.1.懒汉式(Lazy Initialization)

  1. 实例化时机:懒汉式单例在第一次被使用时才会创建实例,即延迟初始化。

  2. 线程安全性:标准的懒汉式实现不是线程安全的。需要引入锁机制(如双重检查锁定)来保证线程安全。

  3. 资源利用率:由于延迟初始化,懒汉式可能在某些情况下减少资源占用,但这也取决于具体实现和使用场景。

  4. 性能开销:在多线程环境下,可能因为锁机制引入额外的性能开销。

  5. 实现示例:

    class LazySingleton {
    private:
        static LazySingleton* instance;
        static std::mutex mutex;
    
        LazySingleton() {} // 私有构造函数
    
    public:
        static LazySingleton* getInstance() {
            if (instance == nullptr) {
                std::lock_guard<std::mutex> lock(mutex);
                if (instance == nullptr) {
                    instance = new LazySingleton();
                }
            }
            return instance;
        }
    };
    
    LazySingleton* LazySingleton::instance = nullptr;
    std::mutex LazySingleton::mutex;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

1.2.饿汉式(Eager Initialization)

  1. 实例化时机:饿汉式单例在程序启动时就立即创建实例,即立即初始化。

  2. 线程安全性:饿汉式实现是线程安全的,因为它在程序启动时就完成了实例化,不存在多线程竞争的问题。

  3. 资源利用率:饿汉式可能会在程序启动时就占用资源,即使该单例实例尚未被使用。

  4. 性能开销:由于避免了锁机制,饿汉式在单例获取时通常具有较高的性能。

  5. 实现示例:

    class EagerSingleton {
    private:
        static EagerSingleton instance; // 静态实例
    
        EagerSingleton() {} // 私有构造函数
    
    public:
        static EagerSingleton& getInstance() {
            return instance;
        }
    };
    
    EagerSingleton EagerSingleton::instance;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

1.3.选择建议

  • 如果单例对象的创建成本较高,且在程序早期不需要立即使用,懒汉式可能更合适,因为它可以延迟对象的创建。
  • 如果需要确保线程安全,且对单例对象的创建时机没有特别要求,饿汉式是一个简单且有效的选择。
  • 在多线程环境中,如果使用懒汉式,需要确保通过适当的同步机制(如双重检查锁定)来保证线程安全。

两种实现方式各有优缺点,应根据具体应用场景和需求来选择最合适的实现策略。

2.单例模式 Singleton 实现方式及详细介绍

在 C++中,单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。以下是单例模式的实现方式及详细介绍:

2.1. 懒汉式(线程不安全)

懒汉式单例模式在第一次使用时才会创建实例。

class Singleton {
private:
    static Singleton* instance;

    Singleton() {} // 私有构造函数

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

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    // 其他成员函数和数据成员
    void doSomething() {
        // ...
    }
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
  • 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
  • 在上述代码中, getInstance 函数在第一次被调用时才会创建Singleton类的实例。如果该函数后续被再次调用,将直接返回已经创建的实例。

2.2. 懒汉式(线程安全)

  • 懒汉式单例模式在多线程环境下可能会出现问题。如果多个线程同时进入getInstance函数并且instance为nullptr,那么可能会创建多个实例。
  • 为了解决这个问题,可以使用互斥锁来确保在多线程环境下只有一个线程能够创建实例。例如:
#include 

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mutex;

    Singleton() {} // 私有构造函数

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

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
    
    // 其他成员函数和数据成员
    void doSomething() {
        // ...
    }
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
  • 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

2.3. 饿汉式(线程安全)

饿汉式单例模式在程序启动时就创建实例。

class Singleton {
private:
    static Singleton instance; // 静态实例

    Singleton() {} // 私有构造函数

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

public:
    static Singleton& getInstance() {
        return instance;
    }
    
    // 其他成员函数和数据成员
    void doSomething() {
        // ...
    }
};

// 实现静态成员变量
Singleton Singleton::instance;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2.4. C++11后 局部静态变量(线程安全)

  • 实现原理
    • C++11 引入了线程安全的静态局部变量初始化特性。利用这个特性,可以在获取单例实例的方法中声明一个静态局部变量,该变量在第一次调用方法时被初始化,并且保证线程安全。
    • 利用局部静态变量的线程安全特性实现单例模式。
class Singleton {
private:
    Singleton() {} // 私有构造函数

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

public:
    static Singleton& getInstance() {
        static Singleton instance; // 局部静态变量
        return instance;
    }
    
    // 其他成员函数和数据成员
    void doSomething() {
        // ...
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.5. 注意事项

  • 单例类的构造函数、拷贝构造函数和赋值操作符通常都是私有的,以防止创建多个实例。
  • 需要确保单例的析构函数是公共的,以便能够销毁单例对象。
  • 在多线程环境中,需要特别注意线程安全问题,可以选择使用锁或原子操作来保证线程安全。

单例模式在实际应用中非常广泛,比如配置管理器、数据库连接池管理器等场景。然而,过度使用单例可能导致代码难以测试和维护,因此应谨慎使用。

3.单例模式的应用场景

  • 日志系统
    • 在一个应用程序中,通常只需要一个日志系统来记录各种信息。使用单例模式可以确保无论在程序的哪个部分调用日志系统,都只有一个实例在运行,避免了多个日志系统之间的冲突和资源浪费。
    • 例如,可以将日志系统实现为单例类,提供统一的接口来记录不同级别的日志信息。
  • 数据库连接
    • 在访问数据库时,通常需要建立一个数据库连接。使用单例模式可以确保只有一个数据库连接实例存在,避免了重复建立连接的开销和资源浪费。
    • 例如,可以将数据库连接类实现为单例类,提供方法来执行数据库查询和更新操作。
  • 配置管理
    • 应用程序的配置信息通常在整个程序中都需要访问。使用单例模式可以确保只有一个配置管理实例存在,方便在程序的任何地方获取和修改配置信息。
    • 例如,可以将配置管理类实现为单例类,提供方法来读取和修改配置文件中的参数。

总之,单例模式在 C++ 中是一种非常有用的设计模式,它可以确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。在实际应用中,可以根据具体情况选择懒汉式或饿汉式单例模式,并注意线程安全和资源管理等问题。

4.单例模式在多线程环境下如何保证线程安全?

在多线程环境下保证单例模式的线程安全,可以采用以下几种方法:

4.1. 双重检查锁定(Double-Checked Locking, DCL)

双重检查锁定是一种常见的线程安全单例实现方式,它减少了锁的开销。

#include 

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mutex;

    Singleton() {} // 私有构造函数

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

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) { // 再次检查实例是否已被创建
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
  • 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

4.2. 局部静态变量

利用局部静态变量的线程安全特性,编译器和其他运行时环境保证了局部静态变量的线程安全初始化。

class Singleton {
private:
    Singleton() {} // 私有构造函数

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

public:
    static Singleton& getInstance() {
        static Singleton instance; // 局部静态变量
        return instance;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

4.3. 原子操作

使用原子操作来确保实例化过程的原子性,避免多线程同时创建实例。

#include 
#include 

class Singleton {
private:
    static std::atomic<bool> initialized;
    static Singleton* instance;
    static std::mutex mutex;

    Singleton() {} // 私有构造函数

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

public:
    static Singleton* getInstance() {
        if (!initialized.load()) {
            std::lock_guard<std::mutex> lock(mutex);
            if (!initialized.load()) {
                instance = new Singleton();
                initialized.store(true);
            }
        }
        return instance;
    }
};

// 初始化静态成员变量
std::atomic<bool> Singleton::initialized(false);
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
  • 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

4.4. 使用 std::call_once 和 std::once_flag

C++11 引入了 std::call_once 函数,它保证在多线程环境下对给定函数的首次调用是单线程执行的。

#include 

class Singleton {
private:
    static std::mutex mutex;
    static std::once_flag onceFlag;
    static Singleton* instance;

    Singleton() {} // 私有构造函数

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

public:
    static Singleton* getInstance() {
        std::call_once(onceFlag, [](){
            instance = new Singleton();
        });
        return instance;
    }
};

// 初始化静态成员变量
std::mutex Singleton::mutex;
std::once_flag Singleton::onceFlag;
Singleton* Singleton::instance = nullptr;
  • 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

4.5. 注意事项

  • 双重检查锁定需要使用 std::atomic 或者合适的锁机制来避免多线程环境下的竞态条件。
  • 局部静态变量的方法简单且线程安全,但可能在程序的生命周期内无法释放单例对象占用的资源。
  • std::call_once 是C++11引入的,它提供了一种优雅的方式来保证线程安全的单例模式实现。

在设计单例模式时,需要根据实际的应用场景和需求来选择合适的线程安全实现方式。

5.除了懒汉式和饿汉式,还有哪些实现单例模式的方法?

除了懒汉式和饿汉式,C++中还有其他几种实现单例模式的方法,包括使用C++11标准引入的线程安全静态局部变量、基于函数的局部静态变量、以及使用智能指针。以下是这些方法的示例:

5.1. 线程安全静态局部变量(C++11)

C++11标准规定,静态局部变量在首次使用时会自动初始化,并且是线程安全的。

class Singleton {
private:
    Singleton() {} // 私有构造函数

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

public:
    static Singleton& getInstance() {
        static Singleton instance; // 线程安全的静态局部变量
        return instance;
    }
    
    // 其他成员函数和数据成员
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

5.2. 基于函数的局部静态变量

在C++中,函数内部的静态变量在首次调用函数时初始化,并且具有线程安全的特性。

class Singleton {
private:
    Singleton() {} // 私有构造函数

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

public:
    static Singleton& getInstance() {
        static Singleton& instance = createInstance();
        return instance;
    }

private:
    static Singleton& createInstance() {
        static Singleton instance;
        return instance;
    }
};

// 其他成员函数和数据成员
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

5.3. 使用 std::call_once 和 std::once_flag

C++11引入了std::call_once,它用于确保在多线程环境下对某个函数的调用是单次的。

#include 

class Singleton {
private:
    static std::once_flag onceFlag;
    static Singleton* instance;

    Singleton() {} // 私有构造函数

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

public:
    static Singleton* getInstance() {
        std::call_once(onceFlag, [](){
            instance = new Singleton();
        });
        return instance;
    }
    
    // 确保析构函数是公共的,以便能够销毁单例对象
    ~Singleton() {}
};

// 初始化静态成员变量
std::once_flag Singleton::onceFlag;
Singleton* Singleton::instance = nullptr;
  • 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

5.4. 使用智能指针

使用std::shared_ptr来管理单例实例,可以自动处理内存释放。

#include 

class Singleton {
private:
    static std::shared_ptr<Singleton> instance;

    Singleton() {} // 私有构造函数

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

public:
    static std::shared_ptr<Singleton> getInstance() {
        if (instance == nullptr) {
            instance.reset(new Singleton());
        }
        return instance;
    }
    
    // 其他成员函数和数据成员
};

// 初始化静态成员变量
std::shared_ptr<Singleton> Singleton::instance;
  • 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

5.5. 注意事项

  • 选择实现单例模式的方法时,需要考虑线程安全性、性能、资源管理等因素。
  • 确保单例的析构函数是公共的,以便能够销毁单例对象。
  • 智能指针方法虽然可以自动管理内存,但在某些情况下可能会引起循环引用或其他问题,需要谨慎使用。

这些方法各有优缺点,可以根据项目的具体需求和约束来选择最合适的实现方式。


关于作者

  • 微信公众号:WeSiGJ
  • GitHub:https://github.com/wesigj/cplusplusboys
  • CSDN:https://blog.csdn.net/wesigj
  • 微博:
  • -版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
WeSiGJ
微信公众号
共同分享,共同交流, 共同学习!
注:本文转载自blog.csdn.net的WeSiGJ的文章"https://blog.csdn.net/wesigj/article/details/141671729"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top