C++设计模式——单例类

C++设计模式——单例类

本文假设有一个Manager管理类,探讨单例类懒汉/饿汉模式的实现,和单例类的多线程安全性,最后介绍Meyers Singleton写法。

懒汉模式#

当第一次要用单例类的时候,再产生实例。是一种典型的拖延(lazy)策略。

类声明:

Copy
class Manager { public: ~Manager(); static Manager* getInstance();//提供单例对象访问 static void deleteInstance();//删除单例对象 void dosomething(); protected: Manager();//构造函数声明为 protected static Manager* s_Manager;//单例对象指针 };

类定义:

Copy
//单例对象指针初始化为nullptr,防止指向了未定义的数据 Manager* Manager::s_Manager = nullptr; //提供单例类对象访问 Manager* Manager::getInstance(){ //当没有存在实例时(一般是指准备第一次用)时,才生成新实例 if(!s_Manager) s_Manager = new CacheManger(); return s_Manager; } //删除单例类 void Manager::deleteInstance(){ if(s_Manager){ deleted s_Manager; s_Manager = nullptr;//别忘了赋予空指针,否则指向未定义数据 } } void Manager::dosomething(){ //dosometing }

使用此类时就可以通过:

Copy
Manager::getInstance()->dosomething();

来运用单例类来做某些操作了。

懒汉模式with线程安全#

上面的例子,并不能保证线程安全。

假如没有实例时,某两个线程都几乎同时使用getInstance(),那么很可能会产生2份实例,其中一份还会变成泄露的内存。

为了解决线程安全问题,本文使用了C++11<mutex>std::mutex作为互斥锁,在类额外增加了一个静态变量std::mutext s_mtx;

Copy
//提供单例类对象访问 Manager* Manager::getInstance() { if (!s_Manager) //检查 {//上锁 std::lock_guard<std::mutex> lock(s_mtx); if (!s_Manager) s_Manager = new Manager(); }//解锁 return s_Manager; } //删除单例类 void Manager::deleteInstance() { if (s_Manager) //检查 {//上锁 std::lock_guard<std::mutex> lock(s_mtx); if (s_Manager) { delete s_Manager; s_Manager = nullptr; } }//解锁 }

为什么不是(上锁,检查,操作,解锁)或者(检查,上锁,操作,解锁),而是使用了双重检查(检查,上锁,检查,操作,解锁)?

  1. 上锁的成本远远比检查空指针要高,且当需要产生实例时才需要锁操作。而实际上大量多次使用getInstance时(因为已经产生了实例)并不需要上锁,若先上锁,则会严重造成性能阻塞。
  2. 仅仅是检查后再上锁,则根本没有做到任何线程安全。

饿汉模式#

饿汉模式与懒汉模式相反,一开始就生成唯一实例。这样就不用检查是否存在实例,而且也无需考虑产生实例时的线程安全。

Copy
class Manager { public: ~Manager(); //提供单例对象访问 static Manager* getInstance(); void dosomething(); protected: //构造函数声明为 保护方法 Manager(); //单例对象指针 static Manager* s_Manager; }; //提供单例类对象访问 Manager* Manager::getInstance(){ return s_Manager; }

使用方法:

Copy
Manager::getInstance()->dosomething();

可以看到代码比懒汉模式简单多了。在大量使用检查空指针造成的性能瓶颈而内存始终充足时,可以考虑使用饿汉模式

Meyers Singleton#

目前最推荐的C++单例写法

Copy
class Manager { public: static Manager& Instance() { static Manager theManager; return theManager; } private: Manager(); Manager(Manager const&); Manager& operator = (Manager const&); ~Manager(); };

这段代码很简单,虽然看上去和懒汉模式类似,只是static变量的位置从类移动到了实例获取函数内。但是实际上由于C++的机制,当第一次调用该函数时,实例才会被构建出来。这样既可以得到饿汉模式的线程安全,又可以有懒汉模式的按需分配的功能。

应用场景#

单例类设计模式算是比较经典的一个模式,但是需要注意,它并不是想象中那么美好。

  1. 它是一种换皮的全局变量。
  2. 它促进了耦合。
  3. 它可能对并发不友好(取决于你使用的单例写法)。

一些替代方案:

  1. 当你仅需要全局可见的方法时,应该用类静态方法而不是一个类实例。
  2. 尽可能为实例提供其它便捷的访问方式(传参/基类获取/服务定位器获取等),而不是通过提供全局可见的访问方式。
  3. 如果你只是需要类保证只有唯一对象而不需要全局性,那么应对外封闭获取实例接口(其实就是不可全局获取实例的)。

所以要注意单例类设计模式不应被泛用,通过上面的替代方案多多少少也就减少了很多不必要的单例设计。

posted @   KelvinVS  阅读(1215)  评论(0编辑  收藏  举报
编辑推荐:
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示
CONTENTS