设计模式:单例模式

     单例模式(Singleton Pattern)是软件工程中一种常见的设计模式,它属于创建型模式。单例模式确保一个类仅有一个实例,并提供一个访问它的全局访问点。这种模式在处理共享资源的场景中非常有用,比如数据库连接等。

单例模式的特点

  1. 私有的构造函数:防止外部实例化。必须自行创建这个实例
  2. 静态变量:持有唯一实例。一个类只有一个实例
  3. 静态公有方法:提供全局访问点来获取这个唯一的实例。必须向整个系统提供这个实例

工作原理

  1. 延迟加载(Lazy Initialization):静态内部类(或在C++中使用局部静态变量)的方式实现了真正的懒加载。这意味着实例只有在第一次调用获取实例的方法时才会被创建,而不是在类加载时就创建。这样可以避免不必要的资源消耗。

  2. 线程安全性:在C++11及之后的标准中,局部静态变量的初始化是线程安全的。也就是说,如果多个线程同时访问该静态变量的初始化代码,只会有一个线程执行初始化,其他线程将会等待,直到初始化完成。这保证了即使在多线程环境下也能正确地初始化单例实例。

  3. 防止反射和反序列化攻击:虽然C++不像Java那样面临反射和反序列化的攻击问题,但静态内部类方法依然提供了一种简单且有效的实现单例模式的方法。

单例模式的实现方式

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

这是最基本的单例模式实现之一,实例在第一次使用时创建

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}  // 私有构造函数
    
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    // 删除拷贝构造函数和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

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

缺点:线程不安全,多线程环境下可能创建多个实例。

2. 懒汉式(线程安全,加锁)

#include 

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;
    Singleton() {}
    
public:
    static Singleton* getInstance() {
        std::lock_guard lock(mtx);
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

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

缺点:每次获取实例都要加锁,性能较差。

3. 懒汉式(双重检查锁定,DCLP)

#include 
#include 

class Singleton {
private:
    static std::atomic instance;
    static std::mutex mtx;
    Singleton() {}
    
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard lock(mtx);
            tmp = instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                std::atomic_thread_fence(std::memory_order_release);
                instance.store(tmp, std::memory_order_relaxed);
            }
        }
        return tmp;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

std::atomic Singleton::instance(nullptr);
std::mutex Singleton::mtx;

优点:线程安全且只在第一次创建时加锁,性能较好。

4. 饿汉式(线程安全)

与懒汉式不同,饿汉式在类加载时就初始化实例,保证了线程安全性,但可能会导致资源浪费,如果实例占用资源较多且不一定会被使用的话。

class Singleton {
private:
    static Singleton instance;
    Singleton() {}
    
public:
    static Singleton& getInstance() {
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

// 在程序开始前就初始化
Singleton Singleton::instance;

优点:线程安全,实现简单
缺点:无论是否使用都会创建实例,可能浪费资源

5. Meyers' Singleton(C++11后最佳实现)

class Singleton {
private:
    Singleton() {}
    
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点

  • 线程安全(C++11保证局部静态变量的线程安全)

  • 懒加载(第一次调用时才会创建)

  • 实现简单

  • 自动处理销毁

单例模式的适用场景

  1. 需要频繁创建和销毁的对象

  2. 创建对象时耗时过多或耗资源过多,但又经常用到的对象

  3. 工具类对象

  4. 频繁访问数据库或文件的对象

  5. 需要控制资源的情况下,如线程池、缓存、日志对象、对话框、打印机、显卡等设备驱动程序

单例模式的优缺点

优点

  • 提供了对唯一实例的受控访问

  • 由于在系统内存中只存在一个对象,因此可以节约系统资源

  • 允许可变数目的实例(可以扩展为多例模式)

缺点

  • 单例模式没有抽象层,因此扩展困难

  • 单例类的职责过重,在一定程度上违背了"单一职责原则"

  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出

单例模式与全局变量的区别

  1. 单例模式是延迟初始化(懒汉式)或按需初始化,而全局变量是程序启动时就初始化

  2. 单例模式可以继承和扩展,全局变量不行

  3. 单例模式提供了更好的访问控制

  4. 单例模式可以方便地改为多例模式

在实际开发中,推荐使用Meyers' Singleton实现方式,它是C++11后最简单、最安全的单例实现方式。

你可能感兴趣的:(设计模式,设计模式,单例模式,C++)