「软件设计模式」单例模式

深入解析单例模式:从思想到C++实战实现

一、设计模式与单例模式思想

1.1 设计模式的价值

设计模式是软件工程领域的经验结晶,如同建筑领域的经典蓝图。它们提供了经过验证的解决方案模板,能有效解决以下问题:

  • 提高代码复用性
  • 提升系统可维护性
  • 增强代码可读性
  • 降低模块耦合度

1.2 单例模式核心思想

单例模式(Singleton Pattern)确保一个类:

  1. 仅有一个实例存在
  2. 提供全局访问点
  3. 严格控制实例化过程

适用场景包括:

  • 配置管理器
  • 日志记录器
  • 线程池
  • 数据库连接池
  • 硬件接口访问

二、C++实现方案对比

2.1 基础懒汉式(线程不安全)

复制代码
cpp 复制代码
#include <iostream>
using namespace std;
// 基础懒汉模式,非线程安全
class Singleton {
public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

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

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

private:
    Singleton() = default;
    ~Singleton() = default;

    static Singleton* instance;
};

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


#include <thread>
#include <vector>

void threadFunction(int i) {
    Singleton* singleton = Singleton::getInstance();
    cout << "Thread " << i << " Singleton address: " << singleton << endl;
    singleton->doSomething();
}

int main() {
    const int numThreads = 10;
    std::vector<std::thread> threads;

    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(threadFunction, i);
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

运行结果:可以看出非线程安全的,创建了两个实例。

2.2 线程安全双检锁(C++11+)

复制代码
cpp 复制代码
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

using namespace std;
class ThreadSafeSingleton {
public:
    static ThreadSafeSingleton* getInstance() {
        if (!instance) {
            std::lock_guard<std::mutex> lock(mutex);
            if (!instance) {
                instance = new ThreadSafeSingleton();
            }
        }
        return instance;
    }

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

private:
    static ThreadSafeSingleton* instance;
    static std::mutex mutex;
};

// 初始化静态成员
ThreadSafeSingleton* ThreadSafeSingleton::instance = nullptr;
std::mutex ThreadSafeSingleton::mutex;
cpp 复制代码
#include <thread>
#include <vector>
#include "lazy_single_thread_safe.h"


void LazySafeThreadFunction(int i) {
    ThreadSafeSingleton* singleton = ThreadSafeSingleton::getInstance();
    cout << "Thread Safe" << i << " Singleton address: " << singleton << endl;
    singleton->doSomething();
}

// 线程安全懒汉模式 demo
int main() {
    const int numThreads = 10;
     std::vector<std::thread> threads1;

    for (int i = 0; i < numThreads; ++i) {
        threads1.emplace_back(LazySafeThreadFunction, i);
    }

    for (auto& thread : threads1) {
        thread.join();
    }
    return 0;
}

运行结果:线程安全。

2.3 现代C++实现(最优方案)

cpp 复制代码
#include <iostream>
using namespace std;

/***
 *  饿汉式
    是否 Lazy 初始化:否
    是否多线程安全:是
    描述:这种方式比较常用,但容易产生垃圾对象。
    优点:没有加锁,执行效率会提高。
    缺点:类加载时就初始化,浪费内存。
    它基于 classloader 机制避免了多线程的同步问题,不过,instance
    在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法,
    但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
#include <iostream>

class EagerSingleton {
public:
    // 获取单例实例的静态方法
    static EagerSingleton* getInstance() {
        static EagerSingleton instance; // 在第一次调用时创建实例
        return &instance;
    }

    // 删除拷贝构造函数和赋值运算符,防止复制
    EagerSingleton(const EagerSingleton&) = delete;
    EagerSingleton& operator=(const EagerSingleton&) = delete;

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

private:
    // 私有构造函数,防止外部实例化
    EagerSingleton() {
        std::cout << "EagerSingleton instance created." << std::endl;
    }
};

运行结果:

方案对比表:

特性 基础懒汉式 双检锁 现代实现
线程安全 ✔️ ✔️
延迟初始化 ✔️ ✔️ ✔️
自动析构 ✔️
C++标准要求 - ≥11 ≥11
实现复杂度 简单 中等 简单

三、关键实现细节解析

3.1 线程安全保证

  • 现代实现方案利用C++11的静态变量初始化特性
  • 编译器保证静态局部变量的线程安全
  • 双检锁方案需要内存屏障支持

3.2 资源管理

  • 现代方案自动处理析构
  • 指针方案需要自定义销毁逻辑
  • 可结合智能指针优化:
cpp 复制代码
#include <memory>

class SmartSingleton {
public:
    static SmartSingleton& getInstance() {
        static auto instance = std::make_shared<SmartSingleton>();
        return *instance;
    }
    // ...其他成员...
};

3.3 继承扩展方案

cpp 复制代码
template <typename T>
class InheritableSingleton {
public:
    static T& getInstance() {
        static T instance;
        return instance;
    }

protected:
    InheritableSingleton() = default;
    virtual ~InheritableSingleton() = default;
};

class MyLogger : public InheritableSingleton<MyLogger> {
    friend class InheritableSingleton<MyLogger>;
    // 具体实现...
};

四、最佳实践与陷阱规避

4.1 使用建议

  1. 优先选择现代实现方案
  2. 明确单例的生命周期
  3. 做好线程安全测试
  4. 考虑依赖注入替代方案

4.2 常见陷阱

  • 循环依赖问题
  • 测试困难(使用虚函数增加可测试性)
  • 多线程环境下的初始化竞争
  • 异常安全性问题

4.3 性能考量

  • 现代方案无锁设计效率最高
  • 双检锁方案需要权衡锁开销
  • 饿汉式初始化可能影响启动速度

五、演进与替代方案

5.1 单例模式演进

  • 多例模式(Multiton)
  • 线程局部单例
  • 集群环境单例

5.2 现代替代方案

  • 依赖注入容器
  • 全局命名空间函数(权衡使用)
  • 服务定位器模式

六、总结

单例模式作为创建型模式的代表,在特定场景下能有效管理系统资源。但需要注意:

  • 不要滥用导致全局状态污染
  • 优先考虑依赖注入等现代方案
  • 关注线程安全和生命周期管理

正确使用单例模式,可以构建出高效、可控的软件系统。但记住,好的架构应该是灵活可扩展的,而不是充满各种全局状态的"单例陷阱"。

示例代码已在GCC 13.1和Clang 16.0测试通过,建议使用C++17及以上标准编译。实际项目中请根据具体需求选择合适的实现方案。

相关推荐
张小姐的猫14 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充17 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充17 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充17 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充17 小时前
2.面向对象的特性
设计模式
杨充17 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充17 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充17 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯20 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法