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


相关推荐: Python爬虫项目实战教程_Scrapy抓取与存储数据实例  Windows如何设置登录时的欢迎屏幕背景?(锁屏界面)  C++如何解析JSON数据?(nlohmann/json库示例)  Windows蓝屏错误0x00000023怎么修复_FAT文件系统错误处理  windows系统如何安装cab更新补丁_windows手动安装更新包教程  PhpStorm怎么调试PHP代码_PhpStorm断点设置与调试启动步骤【指南】  Win11怎么查看已连接wifi密码 Win11查已连wifi密码步骤【教程】  如何正确访问 Laravel 模型或对象的属性而非调用不存在的方法  如何在Golang中配置代码格式化工具_使用gofmt和goimports  如何测试您的网站全球打开速度-网站海外测速工  php在Linux怎么部署_LNMP环境搭建PHP服务的详细指南【指南】  c# F# 的 MailboxProcessor 和 C# 的 Actor 模型  如何优化Golang Web性能_Golang HTTP服务器性能提升方法  Windows10电脑怎么设置文件权限_Win10安全选项卡所有者修改  Win11怎么查看电脑配置_Win11硬件配置详细查询方法【详解】  Mac如何解压zip和rar文件?(推荐免费工具)  Win11怎么设置ip地址_Windows 11手动配置网络IP教程【详解】  如何使用Golang优化模块引入路径_Golanggo mod tidy清理与优化方法  LINUX如何开放防火墙端口_Linux firewalld与iptables开放端口命令【安全配置】  Windows服务无法启动错误1067是什么_进程意外终止的解决方法  Windows10如何重置此电脑_Windows10电脑重置方法【步骤】  LINUX怎么设置系统语言_LINUX修改中文环境  Win10如何更改开机密码_Windows10登录选项更改密码  VSC怎样用终端运行PHP_命令行执行脚本的步骤【教程】  Win10如何设置双wan路由器 Win10双wan路由器设置方法【指南】  Windows系统时间服务错误_W32Time服务修复与同步教学  php增删改查需要哪些扩展_开启mysqli或pdo扩展方法【说明】  Linux怎么查找死循环进程_Linux系统负载分析与进程彻底结束【教程】  c++的mutex和lock_guard如何使用 互斥锁保护共享资源【多线程】  Mac电脑如何恢复出厂设置_Mac抹掉数据并重装系统【安全指南】  Windows 11怎么设置默认解压软件_Windows 11为ZIP/RAR文件指定默认打开程序  Windows10如何更改任务栏高度_Win10解除锁定调整大小  Windows10系统更新错误0x80070002_Win10自动更新失败手动修复  Win11怎么关闭系统声音_Win11系统提示音静音设置【详解】  如何在同包不同文件中正确引用 Go 结构体  Python模块的__name__属性如何由导入方式决定?  Win11怎么修复系统文件_使用sfc命令修复Win11系统【技巧】  Win11如何设置系统语言_Win11系统语言切换教程【攻略】  Python配置文件操作教程_JSONINIYAML解析与应用实战  如何理解Go指针和内存分配关系_Go Pointer内存Model解析  Win11怎么关闭搜索历史 Win11清除搜索框最近记录【隐私】  c++中如何进行二进制文件读写_c++ read与write函数用法  Win11怎么忘记WiFi网络_Win11删除已保存无线连接【教程】  Mac的“预览”如何合并多个PDF_Mac文件处理技巧【效率】  Python与Docker容器化部署实战_镜像构建与CI/CD流程  Python脚本参数接收_sys与argparse解析【指导】  如何在 Go 中调用动态链接库(.so)中的函数  Win10系统更新错误0x80240034怎么办 Win10更新错误解决法【方法】  Win11怎么卸载Photos应用_Win11卸载Photos应用方法【教程】  Mac如何彻底清理浏览器缓存?(Safari与Chrome) 

 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.