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

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

懒汉模式

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

类声明:

class Manager
{
public:
~Manager();
static Manager* getInstance();//提供单例对象访问
static void deleteInstance();//删除单例对象
void dosomething();
protected:
Manager();//构造函数声明为 protected
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 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. 如果你只是需要类保证只有唯一对象而不需要全局性,那么应对外封闭获取实例接口(其实就是不可全局获取实例的)。

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

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

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

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

  2. 游戏设计模式——C++单例类

    前言: 本文将探讨单例类设计模式,单例类的懒汉模式/饿汉模式,单例类的多线程安全性,最后将利用C++模板减少单例类代码量. 本文假设有一个Manager管理类,并以此为探究单例类的设计模式. 懒汉模式 ...

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

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

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

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

  5. (七)boost库之单例类

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

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

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

  7. java设计模式--单例

    GOF23(group of four)---由4个大牛总结的JAVA23种常用的设计模式,归根结底都是为了代码的可扩展性. 设计模式中一种比较重要的思想就是:开闭原则,尽量做到对扩展开放,对修改关闭 ...

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

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

  9. ios开发之 -- 单例类

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

  10. Singleton单例类模式

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

随机推荐

  1. Python面向对象——面向对象介绍、实现面向对象编程、定义类、再调用类产生对象、总结__init__方法、查找顺序

    文章目录 面向对象介绍 实现面向对象编程 一:先定义类 二:再调用类产生对象 总结__init__方法 查找顺序 面向对象介绍 ''' 面向过程: 核心是"过程"二字 过程的终极奥 ...

  2. 2020/5/8—cf,我裂开来

    呜呜呜我爆零了呜呜呜ljll 嗯T1T2防爆零的没了呜呜呜在此纪念可怜的yjz大佬21发AC 太惨了(逃 先来说说我们都有些啥题目吧... T1 嗯,裂开了,当场裂开我一看!桶排!然后实现,嗯?嗯!嗯 ...

  3. 鬼谷子的钱袋(lgP2320)

    主要思路:二进制拆分. 先将 \(m\) 进行二进制拆分. 注意金币总数有限,也就是说拆分后可能会多出来一组.多出来的这组如果不是 \(2^n\) 就不需要考虑了,因为不会和前面的重复. 接下来考虑重 ...

  4. 空地一体化网络综述_Space-Air-Ground Integrated Network: A Survey

    摘要 空地一体化网络(SAGIN)主要解决的是单一网络下的局限性问题,此综述文章从网络设计.资源分配.到性能的优化,对近几年SAGIN的总结. 引言 受限于网络容量和覆盖范围,仅依靠地面通信系统无法在 ...

  5. 机器学习实战5-KMeans聚类算法

    概述 聚类 VS 分类 有监督学习 VS 无监督学习 sklearn中的聚类算法 KMeans KMeans参数&接口 n_clusters n_clusters就是KMeans中的K就是告诉 ...

  6. PTA乙级1038C++哈希解法

    #include"bits/stdc++.h" using namespace std; int main() { int a,b[105]={0}; long i,n,K; ci ...

  7. Python学习 —— 内置数据类型

    写在前面 接上文<Python学习 -- 初步认知>,有需要请自取:Python学习 -- 初步认知 在这篇文章中,我们一起深入了解Python中常用的内置数据类型.Python是一种功能 ...

  8. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-28-处理日历时间控件-上篇

    1.简介 我们在实际工作中,有可能遇到有些web产品,网页上有一些时间选择,然后支持按照不同时间段范围去筛选数据,例如:我们预定火车票或者预定酒店,需要选择发车日期或者酒店的入住与退房时间.宏哥早在之 ...

  9. Net 高级调试之十:轻量级代码生成的调试

    一.简介 今天是<Net 高级调试>的第十篇文章.说起来,高级调试,调试的内容还是挺多的,技巧也不少,但是,要想做一个合格的高级调试人员,还需要掌握如何调试动态生成的IL代码.今天要探讨的 ...

  10. 4个LED流水灯

    #include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器 #include<intrins.h> //因为要用到左右移函数,所以加入这个头文件 ...