前言:

本文将探讨单例类设计模式,单例类的懒汉模式/饿汉模式,单例类的多线程安全性,最后将利用C++模板减少单例类代码量。

本文假设有一个Manager管理类,并以此为探究单例类的设计模式。


懒汉模式

懒汉模式:顾名思义,是一种典型的拖延(lazy)策略。当第一次要用单例类的时候,再产生实例。

类声明:

class Manager{
public:
~Manager();
//提供单例对象访问
static Manager* getInstance();
//删除单例对象
static void deleteInstance();
void dosomething();
protected:
//构造函数声明为 保护方法
Manager();
//单例对象指针
static Manager* s_Manager;
};
//单例对象指针初始化为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
}

这样我们就能在平时的程序用

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

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

懒汉模式with线程安全

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

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

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

//提供单例类对象访问
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. 仅仅是检查后再上锁,则根本没有做到任何线程安全。

饿汉模式

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

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

使用方法:

Manager::getInstance().dosomething();

可以看到代码比懒汉模式简单多了。

在大量使用检查空指针造成的性能瓶颈而内存始终充足时,可以考虑使用饿汉模式

Meyers Singleton(目前最推荐的C++单例写法)

class Singleton {
public:
  static Singleton& Instance() {
   static Singleton theSingleton;
   return theSingleton;
  }
private:
  Singleton();
  Singleton(Singleton const&);
  Singleton& operator=(Singleton const&);
  ~Singleton();
};

这段代码很简单,虽然看上去和懒汉模式类似,只是static变量的位置从类移动到了实例获取函数内。

但是实际上由于C++的机制,当第一次调用该函数时,实例才会被构建出来。

这样既可以得到饿汉模式的线程安全,又可以有懒汉模式的按需分配的功能。

应用场景注意

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

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

一些替代方案:

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

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


游戏设计模式系列-其他文章:

https://www.cnblogs.com/KillerAery/category/1307176.html

游戏设计模式——C++单例类的更多相关文章

  1. 设计模式——懒汉式单例类PK饿汉式单例类

    前言 我们都知道生活中好多小软件,有的支持多IP在线,有的仅仅局限于单个IP在线.为什么这样设计,在软件开发阶段就是,有需求就是发展.这就是软件开发的一个设计模式--懒汉式单例类和饿汉式单例类. 内容 ...

  2. 设计模式(java) 单例模式 单例类

    ·单例类 单实例类,就是这个类只能创建一个对象,保证了对象实例的唯一性. 1.单例模式( Singleton Pattern) 是一个比较简单的模式, 其定义如下:Ensure a class has ...

  3. iOS中编写单例类的心得

    单例 1.认识过的单例类有哪些: NSUserDefaults.NSNotificationCenter.NSFileManager.UIApplication 2.单例类 单例类某个类在代码编写时使 ...

  4. iOS 设计模式之单例

    设计模式:单例 一.  单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并 ...

  5. OC中的单例设计模式及单例的宏抽取

    // 在一个对象需要重复使用,并且很频繁时,可以对对象使用单例设计模式 // 单例的设计其实就是多alloc内部的allocWithZone下手,重写该方法 #pragma Person.h文件 #i ...

  6. (七)boost库之单例类

    (七)boost库之单例类 一.boost.serialzation的单件实现 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一 ...

  7. GCD实现简单的单例类-Singletion

    什么是单例模式 1.单例模式是一个类在系统中只有一个实例对象.通过全局的一个入口点对这个实例对象进行访问.在 iOS 开发中,单例模式是非常有用的一种设计模式.如 下图,是一个简单单例模式的 UML ...

  8. C++解析(27):数组、智能指针与单例类模板

    0.目录 1.数组类模板 1.1 类模板高效率求和 1.2 数组类模板 1.3 堆数组类模板 2.智能指针类模板 2.1 使用智能指针 2.2 智能指针类模板 3.单例类模板 3.1 实现单例模式 3 ...

  9. ios开发之 -- 单例类

    单例模式是一种软件设计模式,再它的核心结构中指包含一个被称为单例类的特殊类. 通过单例模式可以保证系统中一个类只有一个势力而且该势力易于外界访问,从而方便对势力个数的控制并节约系统资源.如果希望在系统 ...

随机推荐

  1. Windows 7下Node.js Web开发环境搭建笔记

    Node.js是什么? 我们看看百科里怎么说的?JavaScript是一种运行在浏览器的脚本,它简单,轻巧,易于编辑,这种脚本通常用于浏览器的前端编程,但是一位开发者Ryan有一天发现这种前端式的脚本 ...

  2. Tcp连接和断开

    三次握手:客户端为a,服务端为b:开始都是closed状态:a主动打开进入到syn_sent状态,b被动打开进入listen状态:第一次握手,a向b发送SYN=1,seq为x的包,b收到以后进入syn ...

  3. glibc提供的malloc()的调试工具

    关键词:MALLOC_CHECK_.mtrace().muntrace().MALLOC_TRACE.mprobe().-lmcheck等等. 1. MALLOC_CHECK_环境变量(double ...

  4. ACM 基本输入

    单次输入 C语言 int a,b; scanf("%d %d",&a,&b); C++语言 int a,b; cin >> a >> b; ...

  5. RabbitMQ学习笔记(四、RabbitMQ队列)

    目录: 消息路由失败了会怎样 备份交换器 TTL与DLX 如何实现延迟队列 RabbitMQ的RPC实现 持久化 事务 发送方确认机制 消息路由失败了会怎样: 在RabbitMQ中,如果消息路由失败了 ...

  6. 第四章 返回结果的HTTP状态码

    第四章 返回结果的HTTP状态码 HTTP状态码负责表示客户端HTTP请求的返回结果.标记服务端的处理是否正常.通知出现的错误等. 1.状态码的类别  2. 2XX成功 200 OK 表示服务端已正常 ...

  7. 使用 Hbuilder 连接手机调试移动端项目

    点击界面上的浏览器右侧的倒三角.   弹出列表以后,点击最后一行 “设置web服务器...”.继续弹出,点击右下角的“外置Web服务器设置”.   点新建.   弹出框后,填入“名称”和“浏览器运行U ...

  8. 【AtCoder】AtCoder Grand Contest 040 解题报告

    点此进入比赛 \(A\):><(点此看题面) 大致题意: 给你一个长度为\(n-1\).由\(<\)和\(>\)组成的的字符串,第\(i\)位的字符表示第\(i\)个数和第\( ...

  9. Educational Codeforces Round 37 (Rated for Div. 2) E. Connected Components? 图论

    E. Connected Components? You are given an undirected graph consisting of n vertices and edges. Inste ...

  10. [题解向] CF#Global Round 1の题解(A $\to$ G)

    这里是总链接\(Link\). \(A\) 题意:求\(\sum_{i=1}^{k} a_i\times b^{k-i}\)的奇偶性, \(k = \Theta(n \log n)\) --其实很容易 ...