单例模式:C++实现与多线程安全

1. 核心定义与作用(精准版)

定义

单例模式 是一种创建型设计模式,确保一个类有且仅有一个实例 ,并向整个系统提供唯一的全局访问点

核心作用

  1. 控制实例数量 :严格保证类在程序生命周期内只有一个对象
  2. 全局访问:无需传递对象,在代码任意位置获取同一个实例
  3. 解决实际问题
    • 避免多实例竞争资源(日志文件、数据库连接、配置文件、打印机)
    • 减少频繁创建 / 销毁对象的性能开销
    • 统一数据状态,保证全局数据一致性

2. 实现方式:饿汉式 vs 懒汉式(对比 + 代码)

一、饿汉式(静态初始化)

核心 :类加载时就创建实例,以空间换时间

cpp

复制代码
// 饿汉式单例(线程安全,无需加锁)
class Singleton {
private:
    // 1. 构造函数私有化
    Singleton() = default;
    // 2. 禁用拷贝、赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 3. 静态实例(类加载时初始化)
    static Singleton instance;

public:
    // 4. 全局访问点
    static Singleton& getInstance() {
        return instance;
    }
};

// 类外初始化
Singleton Singleton::instance;

优点

  • 天然线程安全(由静态初始化保证)
  • 无锁竞争,访问速度快
  • 实现简单,无坑

缺点

  • 程序启动就初始化,即使不用也占内存
  • 大对象会延长启动时间
  • 无法做到延迟加载

适用场景:实例小、一定会使用、追求高性能


二、懒汉式(延迟初始化)

核心 :第一次使用时才创建实例,以时间换空间

① 基础版(线程不安全)

cpp

复制代码
static Singleton* getInstance() {
    if (instance == nullptr) {  // 多线程下会多次进入
        instance = new Singleton();
    }
    return instance;
}

问题:高并发下会创建多个实例,破坏单例。


3. 多线程安全与优化(面试核心)

① 加锁版(线程安全,但效率低)

cpp

复制代码
static Singleton* getInstance() {
    std::lock_guard<std::mutex> lock(mtx); // 每次都加锁
    if (instance == nullptr) {
        instance = new Singleton();
    }
    return instance;
}

缺点 :99% 的情况实例已存在,仍要加锁,并发性能极差


② 双重检查锁 DCL(Double-Checked Locking)

面试必考:为什么要两次判断?

  • 第一次判断:避免每次都加锁
  • 第二次判断:防止多线程同时通过第一次判断

cpp

复制代码
static Singleton* getInstance() {
    if (instance == nullptr) {       // 第一次检查(无锁)
        std::lock_guard<std::mutex> lock(mtx);
        if (instance == nullptr) {   // 第二次检查(有锁)
            instance = new Singleton();
        }
    }
    return instance;
}

致命问题:指令重排 new 分为三步:

  1. 分配内存
  2. 调用构造函数
  3. 指针赋值给 instance

编译器可能优化为:1 → 3 → 2导致其他线程拿到未构造完成的半成品对象,程序崩溃。

C++11 解决方案 使用 std::atomic + 内存屏障禁止重排。


③ Meyers 单例(C++ 最优写法,强烈推荐)

C++11 特性 :静态局部变量初始化是线程安全的!

cpp

复制代码
class Singleton {
private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    static Singleton& getInstance() {
        static Singleton instance; // 线程安全,延迟加载
        return instance;
    }
};

优点

  • 延迟加载
  • 天然线程安全
  • 无锁、无指针、无内存泄漏
  • 代码极简、无坑

这是 C++ 工程与面试首选写法!


4. C++ 单例必写规范(面试必问)

① 必须私有化构造函数

防止外部 new 创建对象。

② 必须禁用拷贝构造 + 赋值运算符

cpp

复制代码
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

防止通过拷贝产生新实例。

③ 析构函数一般私有化

单例由类管理生命周期,外部不能 delete。

④ 实例类型选择

  • 饿汉:静态对象(栈 / 全局区)
  • 懒汉:静态局部对象(推荐)
  • 不推荐裸指针(内存泄漏风险)

5. 单例生命周期与内存管理

  1. 静态实例 :程序结束时自动释放,无需手动管理
  2. 指针实例 :需要手动写 destroy() 释放,否则内存泄漏
  3. 依赖顺序:多个单例互相依赖时,可能出现析构顺序问题
  4. 最佳方案:使用 Meyers 单例,完全交给系统管理

6. 单例模式优缺点总结

优点

  • 严格控制唯一实例
  • 全局访问方便
  • 避免重复创建销毁开销
  • 避免资源竞争冲突

缺点

  • 扩展性差(很难改成多例)
  • 隐藏依赖关系
  • 多线程下实现复杂(基础版不安全)
  • 不利于单元测试(难以 mock)
相关推荐
用户805533698031 小时前
现代Qt开发教程(新手篇)1.14——日志
c++·qt
艾莉丝努力练剑2 小时前
【Linux网络】Linux 网络编程入门:TCP Socket 编程(下)
linux·运维·服务器·网络·c++·tcp/ip
宵时待雨2 小时前
linux笔记归纳4:进程概念
linux·运维·服务器·c++·笔记
凯瑟琳.奥古斯特3 小时前
力扣2760 C++滑动窗口解法
数据结构·c++·算法·leetcode·职场和发展
ximu_polaris3 小时前
设计模式(C++)-行为型模式-访问者模式
c++·设计模式·访问者模式
血玥珏3 小时前
血玥珏-多WAV/MP3混音合成小工具2.0.0.5
c++·音视频
workflower4 小时前
农业信息化
大数据·人工智能·设计模式·机器人·软件工程
Shadow(⊙o⊙)4 小时前
初识Qt+经典方式实现hello world!的交互
开发语言·c++·后端·qt·学习
梵尔纳多4 小时前
OpenGL 实例化
c++·图形渲染·opengl