c++如何实现单例模式_c++ Singleton模式写法【源码】

发布时间 - 2026-01-31 00:00:00    点击率:
C++单例不能只靠静态局部变量,因C++11仅保证其初始化线程安全,构造异常会导致重复进入,析构顺序不可

控易引发崩溃;推荐静态局部变量+删除拷贝操作,兼顾简洁、线程安全与自动内存管理。

为什么 C++ 单例不能只靠静态局部变量?

因为线程安全不是默认的——C++11 起 static 局部变量的初始化才保证首次调用时的线程安全,但仅限于初始化过程;若构造函数里有耗时操作或抛异常,getInstance() 可能被多次进入,导致未定义行为。更麻烦的是,析构顺序不可控,全局对象析构时若其他单例还在用它,就崩了。

常见错误现象:std::terminate、访问已析构对象、多线程下重复构造、程序退出时崩溃。

  • 必须用 static std::unique_ptr 或双重检查锁定(DCLP)来控制生命周期
  • 禁止在构造函数中调用其他单例的 getInstance()
  • 若需明确析构时机(比如日志单例要最后销毁),得手动调用 reset()

最简安全写法:C++11 静态局部变量 + delete 构造函数

这是目前推荐的默认方案,代码少、线程安全、自动管理内存,适用于绝大多数场景。

class Logger {
public:
    static Logger& getInstance() {
        static Logger instance;  // C++11 线程安全初始化
        return instance;
    }
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

private:
    Logger() = default;  // 私有构造,防止外部 new
    ~Logger() = default; // 析构也私有,但允许静态对象调用
};

注意:static Logger instance 在第一次调用 getInstance() 时构造,程序结束前自动析构;若构造函数抛异常,下次调用仍会重试——这可能是你想要的,也可能不是。

需要手动控制析构?用 std::unique_ptr + call_once

当单例依赖其他资源(如文件句柄、网络连接),且必须在所有其他单例析构后才关闭时,就得自己管生命周期。

关键点:std::call_once 保证只初始化一次,std::unique_ptr 控制堆内存和析构时机。

class Config {
public:
    static Config& getInstance() {
        std::call_once(initFlag, []() {
            instance.reset(new Config);
        });
        return *instance;
    }
    static void destroy() { instance.reset(); } // 显式销毁

private:
    Config() = default;
    static std::unique_ptr instance;
    static std::once_flag initFlag;
};

instance.reset() 会立即调用析构函数;destroy() 必须在所有依赖它的对象销毁后调用,否则后续访问就是悬空引用。

性能影响:比静态局部变量多一次指针解引用和一次 call_once 检查,但几乎可忽略;兼容性好,C++11 起都支持。

别踩坑:std::shared_ptr 不适合做单例句柄

有人用 static std::shared_ptr 返回 shared_ptr,这会导致两个问题:

  • 每次调用 getInstance() 都产生新引用,无法判断单例是否已被销毁
  • 多个 shared_ptr 实例可能延长对象生命周期,违背“全局唯一且可控”的本意

正确做法是始终返回引用(T&)或原始指针(T*),让使用者清楚:这不是一个可共享所有权的对象,而是一个全局服务入口。

另外,模板单例(如 Singleton)看似通用,但容易掩盖类型耦合和初始化顺序问题,除非你真需要几十个不同类型的单例且能严格约束它们的依赖图,否则不建议一开始就上。


# c++  # 为什么  # red  # Static  # 构造函数  # 析构函数  # 局部变量  # 指针  #   # 线程  # 多线程  # delete  # 对象  # 只靠  # 的是  # 是一个  # 这是  # 还在  # 首次  # 多个  # 句柄  # 已被  # 适用于 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 怎么制作网站设计模板图片,有电商商品详情页面的免费模板素材网站推荐吗?  音响网站制作视频教程,隆霸音响官方网站?  标题:Vue + Vuex + JWT 身份认证的正确实践与常见误区解析  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?  如何在阿里云购买域名并搭建网站?  Laravel如何实现模型的全局作用域?(Global Scope示例)  Laravel怎么处理异常_Laravel自定义异常处理与错误页面教程  如何挑选高效建站主机与优质域名?  昵图网官网入口 昵图网素材平台官方入口  Laravel如何处理异常和错误?(Handler示例)  Laravel怎么实现搜索功能_Laravel使用Eloquent实现模糊查询与多条件搜索【实例】  如何快速搭建高效简练网站?  Java遍历集合的三种方式  Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势  晋江文学城电脑版官网 晋江文学城网页版直接进入  javascript日期怎么处理_如何格式化输出  Python进程池调度策略_任务分发说明【指导】  Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知  黑客如何利用漏洞与弱口令入侵网站服务器?  奇安信“盘古石”团队突破 iOS 26.1 提权  Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】  如何在阿里云虚拟机上搭建网站?步骤解析与避坑指南  javascript中的数组方法有哪些_如何利用数组方法简化数据处理  如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环  如何实现javascript表单验证_正则表达式有哪些实用技巧  VIVO手机上del键无效OnKeyListener不响应的原因及解决方法  Laravel如何使用Spatie Media Library_Laravel图片上传管理与缩略图生成【步骤】  Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践  C#如何调用原生C++ COM对象详解  Laravel Asset编译怎么配置_Laravel Vite前端构建工具使用  ,交易猫的商品怎么发布到网站上去?  EditPlus中的正则表达式 实战(2)  如何快速搭建高效服务器建站系统?  如何正确下载安装西数主机建站助手?  如何在IIS中新建站点并配置端口与物理路径?  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解  如何在阿里云虚拟服务器快速搭建网站?  如何使用 Go 正则表达式精准提取括号内首个纯字母标识符(忽略数字与嵌套)  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  html5怎么画眼睛_HT5用Canvas或SVG画眼球瞳孔加JS控制动态【绘制】  ,网页ppt怎么弄成自己的ppt?  Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】  Laravel怎么导出Excel文件_Laravel Excel插件使用教程  如何在万网自助建站中设置域名及备案?  如何快速搭建FTP站点实现文件共享?  如何获取PHP WAP自助建站系统源码?  网站制作软件免费下载安装,有哪些免费下载的软件网站?  Laravel如何升级到最新版本?(升级指南和步骤)  zabbix利用python脚本发送报警邮件的方法