c++如何实现单例模式_c++ 饿汉式与懒汉式线程安全实现【方法】


饿汉式单例启动即初始化,天然线程安全;懒汉式首次调用才创建,需用std::call_once或双重检查锁定保障线程安全,但易出错,推荐优先使用饿汉式。

饿汉式单例:启动即初始化,天然线程安全

饿汉式在程序加载时就完成实例构造,后续所有调用都直接返回已创建的对象指针,不存在多线程竞争问题,无需加锁。

关键点在于 static 成员变量的初始化时机由编译器保证——C++11 起,static 局部变量的初始化是线程安全的;而静态成员变量(如类内定义的 static Instance*)在 main() 执行前完成,且仅一次。

常见错误是把指针声明和 new 拆开写,导致非原子操作:

class Singleton {
private:
    static Singleton* instance;
    Singleton() = default;  // 防止外部构造
public:
    static Singleton* getInstance() {
        return instance;  // ❌ instance 可能为 nullptr 或未初始化
    }
};
Singleton* Singleton::instance = new Singleton();  // ✅ 此行才真正构造

更推荐写法(C++11+):

立即学习“C++免费学习笔记(深入)”;

class Singleton {
private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
public:
    static Singleton& getInstance() {
        static Singleton instance;  // ✅ 局部静态变量,线程安全初始化
        return instance;
    }
};
  • 必须禁用拷贝构造与赋值,否则可能意外复制出多个对象
  • 返回引用比返回指针更安全,避免用户误删或置空
  • 析构顺序不可控:局部静态对象在 main 结束后按逆序销毁,若其他静态对象依赖它,可能访问已析构对象

懒汉式单例:首次调用才创建,需手动保障线程安全

懒汉式延迟资源占用,但 getInstance() 中的判空 + 构造逻辑不是原子操作,多线程下极易出现重复 new 或返回未完全构造的对象。

典型错误写法(双重检查锁定漏锁):

static Singleton* getInstance() {
    if (instance == nullptr) {           // 第一次检查
        instance = new Singleton();      // ❌ 构造+赋值非原子,可能被重排,其他线程看到半初始化对象
    }
    return instance;
}

正确实现(C++11 double-checked locking pattern):

class Singleton {
private:
    static std::atomic instance;
    static std::mutex mtx;
    Singleton() = default;
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance.load(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();
                instance.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }
};
std::atomic Singleton::instance{nullptr};
std::mutex Singleton::mtx;
  • 必须用 std::atomic 替代裸指针,否则无法防止指令重排
  • memory_order_acquirememory_order_release 保证构造完成后再对其他线程可见
  • 两次判空缺一不可:第一次避免无谓加锁,第二次防止加锁后已被其他线程创建
  • 不建议手写 DCLP —— 容易出错,优先用局部静态变量(饿汉式)或 std::call_once

更现代的懒汉式替代:std::call_once + once_flag

相比手写 DCLP,std::call_once 更简洁、不易出错,且由标准库保证绝对只执行一次。

class Singleton {
private:
    static Singleton* instance;
    static std::once_flag init_flag;
    Singleton() = default;
public:
    static Singleton* getInstance() {
        std::call_once(init_flag, []() {
            instance = new Singleton();
        });
        return instance;
    }
};
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::init_flag;
  • std::call_once 内部已做线程同步,无需额外锁或原子操作
  • 适合初始化逻辑较重、且确实需要延迟加载的场景
  • 注意:instance 仍需声明为 static,且不能在 lambda 外提前使用
  • 析构仍需手动管理(比如用 std::unique_ptr 包裹并注册 atexit),否则内存泄漏

饿汉式 vs 懒汉式:选型关键看初始化成本与依赖关系

饿汉式看似“浪费”,实则规避了绝大多数线程安全陷阱;懒汉式看似灵活,却把复杂性推给了开发者。

真实项目中容易被忽略的点:

  • 如果单例构造函数中调用了其他尚未初始化的全局对象(比如另一个单例),饿汉式可能因静态初始化顺序未定义而崩溃
  • 懒汉式若用 std::call_once,其内部实现依赖 OS 级同步原语,在极低概率下(如 fork 后)可能异常,但绝大多数场景可忽略
  • C++20 引入 constinit,但目前对单例帮助有限,仍无法解决跨编译单元初始化顺序问题
  • 真正需要懒汉式的场景极少——多数所谓“耗资源”其实是错觉,真正瓶颈往往在 I/O 或网络,而非内存分配

除非明确知道构造开销极大、且确定不会引发静态初始化依赖,否则默认用饿汉式(局部静态变量版本)最省心。


# ai  # c++  # 延迟加载  # 标准库  # Static  # 成员变量  # 构造函数  # 局部变量  # double  # Lambda  # 指针  # 线程  # 多线程  # 对象  # 首次  # 加锁  # 仍需  # 加载  # 多个  # 已被  # 两次  # 能在  # 给了 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化76771 】 【 技术知识130152 】 【 IDC云计算60162 】 【 营销推广131313 】 【 AI优化88182 】 【 百度推广37138 】 【 网站推荐60173 】 【 精选阅读31334


相关推荐: Win10电脑怎么设置IP地址_Windows10网络属性固定IP配置  TestNG的testng.xml配置文件怎么写  如何用正则表达式精确匹配最多含一个换行符的起止片段  Win11关机快捷键是什么_Win11快速关机方法【大全】  如何外贸网站设计-能留住客户提升用户体验!  Windows10无法连接到Internet_Win10网络重置命令详解  Windows10怎么查看系统激活状态_Windows10激活状态查看方法【教程】  php中常量能用::访问吗_类常量与作用域操作符使用场景【汇总】  Win11怎么设置触控板手势_Windows11三指四指操作自定义  C#如何序列化对象为XML XmlSerializer用法  Python与GPU加速技术_CUDA与Numba高性能计算实践  Windows11如何设置专注助手_Windows11专注助手使用攻略【技巧】  Mac的“调度中心”与“空间”怎么用_Mac多桌面高效管理【技巧】  Python类装饰器使用_元编程解析【教程】  Win11怎么关闭键盘按键音_Win11禁用打字声音反馈【教程】  XSLT怎么生成动态的HTML属性名和标签名  如何使用Golang实现容器自动化运维_Golang Docker运维管理方法  php嵌入式需要什么环境_搭建php+linux嵌入式开发环境【详解】  Win10怎样设置闹钟贪睡时间 Win10闹钟贪睡时长设置【步骤】  Win10怎样设置多显示器_Win10多显示器扩展设置【攻略】  PHP主流架构怎么部署到Docker_容器化流程【操作】  如何在Windows上设置闹钟和计时器_系统自带的时钟应用全攻略【生活技巧】  Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)  c++如何使用std::bitset进行位图算法_c++ 快速查找与大规模数据排重【方法】  Win11怎么开启远程桌面连接_Windows11系统属性远程设置  Python多进程教程_multiprocessing模块实战  Windows服务无法启动错误1067是什么_进程意外终止的解决方法  php订单日志怎么记录物流_php记录订单物流变更日志指南【指南】  如何使用Golang捕获测试日志_Golang testing日志记录方法  短链接怎么用php还原_从基础原理到代码实现教学【详解】  c++如何利用doxygen生成开发文档_c++ 代码注释规范与HTML文档导出【案例】  Windows10如何更改计算机工作组_Win10系统属性修改Workgroup  如何在 Go 中正确反序列化多个并列的 XML 元素(而非 XML 数组)  Mac电脑进水了怎么办_MacBook进水后紧急处理方法【必看】  使用类变量定义字符串常量时的类型安全最佳实践  Win11如何设置自动关机 Win11定时关机命令使用教程【技巧】  php做exe支持多线程吗_并发处理实现方式【详解】  php485返回数据不完整怎么办_php485数据分包重组处理方法【教程】  Python抽象类与接口设计_规范说明【指导】  Win11怎么设置虚拟键盘_打开Win11屏幕键盘操作指南【技巧】  如何在Golang中实现邮件发送功能_Golang SMTP发送与错误处理示例  英国搜索:多数英国人认为语言搜索是未来搜索  Windows10电脑怎么设置文件权限_Win10安全选项卡所有者修改  Windows10系统怎么查看IP地址_Win10网络连接状态详细信息  Python如何创建带属性的XML节点  如何使用正则表达式提取以编号开头、后跟多个注解的完整代码块  Win10怎样卸载iTunes_Win10卸载iTunes步骤【步骤】  Python 中将 ISO 8601 时间戳转换为日期并计算日期差值的完整教程  Windows 10怎么录屏_Windows 10使用Xbox Game Bar录制屏幕视频教程  Python大文件处理策略_内存优化说明【指导】 

 2026-01-03

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

致胜网络推广营销网


致胜网络推广营销网

致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 915688610

 17370845950

 915688610@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.