C++怎么写单例 C++饿汉式与懒汉式实现【精讲】

2次阅读

推荐使用c++11 Static局部变量实现单例:线程安全、延迟初始化、自动析构;避免饿汉式资源浪费和懒汉式线程安全风险,优先考虑依赖注入替代单例。

C++怎么写单例 C++饿汉式与懒汉式实现【精讲】

饿汉式单例:启动即初始化,线程安全但可能浪费资源

饿汉式在程序加载时就创建实例,天然线程安全,不用加锁,但缺点也很实在:哪怕从不调用 getInstance()对象也会被构造。如果构造开销大(比如读配置、连数据库),或者依赖环境未就绪,就容易出问题。

常见错误现象:std::terminate 或静态初始化顺序崩溃(尤其跨编译单元引用时);构造函数抛异常导致程序直接退出。

  • 必须用 static 局部变量或 static 成员变量 + private 构造函数
  • 推荐用 static 局部变量写法(C++11 起保证线程安全):
    class Singleton { public:     static Singleton& getInstance() {         static Singleton instance;  // C++11 线程安全的延迟初始化         return instance;     }     Singleton(const Singleton&) = delete;     Singleton& operator=(const Singleton&) = delete; private:     Singleton() = default;  // 可含初始化逻辑,但别 throw };
  • 避免在构造函数里做不可靠操作(如文件 I/O、网络请求),否则失败无法恢复

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

懒汉式按需创建,节省资源,但多线程下极易出错——两个线程同时判断 instance == nullptr,都进去 new,结果构造两次、内存泄漏、返回不同地址。

典型错误现象:double freeuse-after-freeundefined behavior,调试时偶发崩溃,难以复现。

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

  • 最简可用方案是加 std::mutex 锁整个 getInstance(),但性能差(每次调用都锁)
  • 推荐双重检查锁定(DCLP):
    class Singleton { public:     static Singleton& getInstance() {         if (instance == nullptr) {             std::lock_guard<std::mutex> lock(mutex_);             if (instance == nullptr) {                 instance = new Singleton();             }         }         return *instance;     }     // ... 析构、拷贝禁用同上 private:     static Singleton* instance;     static std::mutex mutex_;     Singleton() = default; }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::mutex_;
  • 必须用 volatilestd::atomic 修饰 instance 指针(C++11 后建议用 std::atomic<singleton></singleton>),否则编译器重排可能导致部分构造对象被其他线程看到

现代 C++ 更推荐:用 static 局部变量替代手写懒汉

C++11 标准规定:函数内 static 局部变量的初始化是线程安全的,且只执行一次。这直接解决了懒汉式的所有痛点,代码更短、更可靠、性能更好。

使用场景:95% 的单例需求都该用这个写法,除非你必须支持 C++03 或需要显式控制销毁时机。

  • 无需 std::mutex、无需 new / delete、无需指针管理
  • 自动满足“首次调用才构造”+“线程安全”+“自动析构”三要素
  • 注意:析构发生在 main() 返回后、全局对象析构阶段,若单例依赖其他全局对象,销毁顺序可能出问题

单例的真正陷阱:生命周期与测试友好性

单例最难搞的从来不是怎么写,而是它让代码隐式依赖全局状态,导致单元测试难 mock、模块耦合高、重构成本陡增。

常见错误现象:写完单例后发现无法对 A 模块单独测(它总依赖 B 单例),或者换环境时因单例初始化顺序崩了。

  • 优先考虑依赖注入:把单例对象作为参数传入,而非在函数内部硬调 Singleton::getInstance()
  • 如果真要单例,确保它的构造/析构不依赖其他单例,尤其避开跨 .cpp 文件的静态对象互相引用
  • 避免在 main() 之外的静态初始化上下文中调用 getInstance()(比如全局变量初始化器里),否则行为未定义

实际项目里,static 局部变量那几行代码就够用了;但只要它开始影响测试或引发初始化顺序问题,就得回头砍掉单例,换成明确传递的对象。

text=ZqhQzanResources