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++还有哪些常用的设计模式?

相关推荐
1024小神1 小时前
使用AVFoundation实现二维码识别的角点坐标和区域
开发语言·数码相机·ios·swift
Q741_1471 小时前
C++ 栈 模拟 力扣 844. 比较含退格的字符串 题解 每日一题
c++·算法·leetcode·模拟·
喵个咪1 小时前
C++ 类型转换:旧风格与四种新风格详解
c++·后端
扶尔魔ocy1 小时前
C/C++ 聊聊结构体、指针、类
c++·qt·
QQ_4376643141 小时前
分布式RPC网络框架
网络·c++·分布式·rpc
廋到被风吹走1 小时前
【JDK版本】JDK1.8相比JDK1.7 语言特性之函数式编程
java·开发语言·python
y***61311 小时前
PHP操作redis
开发语言·redis·php
fire-flyer1 小时前
Reactor Context 详解
java·开发语言
fy zs1 小时前
Linux线程互斥与同步
linux·c++