C++ 单例模式

单例模式是设计模式中创建型模式 的核心之一,核心目标是:保证一个类在程序生命周期内仅有一个实例 ,并提供一个全局统一的访问入口。懒汉模式(Lazy Singleton)和饿汉模式(Hungry Singleton,也译 "饥饿模式")是单例模式最常见的两种实现方式,核心区别在于实例初始化的时机,以及由此衍生的线程安全、资源占用特性。

一、饿汉模式(饥饿模式)

1. 核心思想

"饿" 意味着类加载时就立即初始化实例,不管后续是否会用到这个实例。实例的创建发生在程序启动 / 类加载阶段,而非第一次调用访问方法时。

2. 实现(C++ 示例)
复制代码
#include <iostream>
using namespace std;

class HungrySingleton {
private:
    // 1. 私有构造函数:禁止外部创建实例
    HungrySingleton() {
        cout << "饿汉模式:实例已初始化(类加载时)" << endl;
    }

    // 2. 私有静态成员变量:类加载时直接初始化实例(全局唯一)
    static HungrySingleton* instance;

public:
    // 3. 公有静态方法:提供全局访问入口
    static HungrySingleton* getInstance() {
        return instance; // 仅返回已创建的实例
    }

    // 禁止拷贝/赋值(保证单例唯一性)
    HungrySingleton(const HungrySingleton&) = delete;
    HungrySingleton& operator=(const HungrySingleton&) = delete;
};

// 类外初始化静态成员(类加载阶段执行,仅一次)
HungrySingleton* HungrySingleton::instance = new HungrySingleton();

// 测试
int main() {
    cout << "程序启动,尚未调用getInstance()" << endl;
    HungrySingleton* s1 = HungrySingleton::getInstance();
    HungrySingleton* s2 = HungrySingleton::getInstance();
    cout << "s1 和 s2 是否指向同一实例:" << (s1 == s2) << endl; // 输出 1(true)
    return 0;
}
3. 核心特点
优点 缺点
① 天然线程安全:类加载由编译器 / 操作系统保证原子性,仅初始化一次,无多线程竞态问题;② 实现简单,无额外同步开销; ① 资源提前占用:若实例初始化耗资源(如大内存、网络连接)且全程未使用,造成资源浪费;② 无法处理 "依赖初始化顺序" 问题:若单例依赖其他未初始化的全局变量,可能导致初始化失败;
4. 适用场景
  • 实例初始化开销小、大概率会被使用的场景;
  • 对线程安全要求高,且不想引入同步锁的场景。

二、懒汉模式

1. 核心思想

"懒" 意味着延迟初始化 :实例不会在类加载时创建,而是第一次调用 getInstance() 方法时才初始化,做到 "用的时候才创建"。

2. 实现(C++ 示例,分版本)
版本 1:基础版(非线程安全)
复制代码
#include <iostream>
using namespace std;

class LazySingleton {
private:
    LazySingleton() {
        cout << "懒汉模式:实例首次初始化(调用getInstance时)" << endl;
    }

    // 静态成员指针:初始化为空,延迟创建
    static LazySingleton* instance;

public:
    static LazySingleton* getInstance() {
        if (instance == nullptr) { // 第一次调用时创建
            instance = new LazySingleton();
        }
        return instance;
    }

    // 禁止拷贝/赋值
    LazySingleton(const LazySingleton&) = delete;
    LazySingleton& operator=(const LazySingleton&) = delete;
};

// 初始化为空
LazySingleton* LazySingleton::instance = nullptr;

// 测试
int main() {
    cout << "程序启动,instance 为空" << endl;
    LazySingleton* s1 = LazySingleton::getInstance(); // 首次调用,创建实例
    LazySingleton* s2 = LazySingleton::getInstance(); // 直接返回已有实例
    cout << "s1 和 s2 是否指向同一实例:" << (s1 == s2) << endl; // 输出 1
    return 0;
}

问题 :多线程同时调用 getInstance() 时,可能进入 if (instance == nullptr) 分支,导致创建多个实例,破坏单例。

复制代码
#include <iostream>
#include <mutex> // 引入互斥锁
using namespace std;

class LazySingleton {
private:
    LazySingleton() {}

    // C++11 后用 volatile/atomic 防止编译器优化导致的指令重排
    static atomic<LazySingleton*> instance;
    static mutex mtx; // 互斥锁

public:
    static LazySingleton* getInstance() {
        // 第一次检查:避免每次调用都加锁(提升性能)
        LazySingleton* tmp = instance.load(memory_order_relaxed);
        if (tmp == nullptr) {
            lock_guard<mutex> lock(mtx); // 加锁
            // 第二次检查:防止加锁期间其他线程已创建实例
            tmp = instance.load(memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new LazySingleton();
                instance.store(tmp, memory_order_relaxed);
                cout << "懒汉模式:线程安全版实例初始化" << endl;
            }
        }
        return tmp;
    }

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

// 初始化静态成员
atomic<LazySingleton*> LazySingleton::instance = nullptr;
mutex LazySingleton::mtx;

// 测试多线程(简化示例)
int main() {
    LazySingleton* s1 = LazySingleton::getInstance();
    LazySingleton* s2 = LazySingleton::getInstance();
    cout << "s1 和 s2 是否同一实例:" << (s1 == s2) << endl;
    return 0;
}
版本 3:C++11 极简线程安全版(局部静态变量)

C++11 标准规定:局部静态变量的初始化是线程安全的(编译器保证仅初始化一次),无需手动加锁,是懒汉模式的最优实现:

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

class LazySingleton {
private:
    LazySingleton() {
        cout << "懒汉模式:C++11 极简版实例初始化" << endl;
    }

public:
    // 局部静态变量:第一次调用时初始化,线程安全
    static LazySingleton& getInstance() {
        static LazySingleton instance;
        return instance; // 返回引用,避免内存泄漏
    }

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

// 测试
int main() {
    LazySingleton& s1 = LazySingleton::getInstance();
    LazySingleton& s2 = LazySingleton::getInstance();
    cout << "s1 和 s2 是否同一实例:" << (&s1 == &s2) << endl; // 输出 1
    return 0;
}
3. 核心特点
优点 缺点
① 延迟加载:仅在使用时初始化,节省资源;② 灵活:可处理依赖初始化顺序的问题; ① 基础版线程不安全,需额外处理同步(加锁 / 依赖 C++11 特性);② 加锁版存在轻微性能开销(第一次调用后无影响);
4. 适用场景
  • 实例初始化开销大、可能不会被使用的场景(如配置类、工具类);
  • 对资源占用敏感,希望 "按需创建" 的场景。

三、懒汉 vs 饿汉 核心对比

维度 饿汉模式 懒汉模式
初始化时机 类加载时(程序启动阶段) 第一次调用 getInstance() 时
线程安全 天然安全(无需额外处理) 基础版不安全,需加锁 / C++11 特性
资源占用 提前占用,可能浪费 延迟占用,按需分配
实现复杂度 简单(无同步逻辑) 稍复杂(需处理线程安全)
内存泄漏风险 静态指针版需手动释放(或用智能指针) 局部静态版无泄漏,指针版需注意

四、总结

  1. 优先选饿汉模式:实现简单、线程安全,适合实例轻量、必被使用的场景;
  2. 选懒汉模式(C++11 局部静态版):资源敏感、实例可能未被使用的场景,极简且线程安全;
  3. 无论哪种模式,都要禁用拷贝构造和赋值运算符,避免通过拷贝创建新实例;
  4. 单例模式的本质是 "控制实例数量",需根据资源开销、线程场景选择实现方式。

饿汉模式和懒汉模式的优缺点对比

单例模式的应用场景有哪些?

除了单例模式,C++还有哪些常用的设计模式?

相关推荐
longlongzihan4 分钟前
前缀积与后缀积 —— LeetCode 238. 除了自身以外数组的乘积
开发语言·c++·leetcode
All for pursuit.8 分钟前
【动态规划-1】121.买卖股票的最佳时机
数据结构·c++·算法·leetcode·动态规划
szial17 分钟前
JavaScript 的 call、apply 和 bind:如何控制函数调用
开发语言·前端·javascript
水饺编程19 分钟前
第1章,[Win32 章节]:API 及 内存管理模式
c语言·c++·windows·visual studio
weixin_3077791319 分钟前
C++代码实现MATLAB中的cvpartition函数功能
开发语言·c++·算法·matlab
黑妹天下第一乖28 分钟前
小智改造实战解读-首 token 延迟去哪了:云端与端侧大模型的分段对照
开发语言·人工智能·python·嵌入式硬件·自然语言处理·iot
。小二29 分钟前
Go 泛型并发利刃:async 库深度评测——370K ops/s、零依赖、生产就绪
开发语言·javascript·golang
(Charon)33 分钟前
【C++面试】线程同步机制:互斥锁、条件变量、原子操作与读写锁
开发语言·c++·算法·面试
CV工程师丁Sir1 小时前
ArkWeb 手记 06|window.open 与页面跳转拦截
开发语言·php·harmonyos
夜不会漫长1 小时前
C++:内存管理
java·开发语言·c++