单例模式:就是只有一个实例。

singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点。这个类称为单例类。如一个工程中,数据库访问对象只有一个,电脑的鼠标只能连接一个,操作系统只能有一个窗口管理器等,这时可以考虑使用单例模式。

众所周知,c++中,类对象被创建时,编译系统为对象分配内存空间,并自动调用构造函数,由构造函数完成成员的初始化工作,也就是说使用构造函数来初始化对象。

1、那么我们需要把构造函数设置为私有的 private,这样可以禁止别人使用构造函数创建其他的实例。

2、又单例类要一直向系统提供这个实例,那么,需要声明它为静态的实例成员,在需要的时候,才创建该实例。

3、且应该把这个静态成员设置为 null,在一个public 的方法里去判断,只有在静态实例成员为 null,也就是没有被初始化的时候,才去初始化它,且只被初始化一次。

通常我们可以让一个全局变量使得一个对象被访问,但它不能阻止你实例化多个对象。如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响。

一个最好的办法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法,单例模式比全局对象好还包括,单例类可以继承。

单例模式又分为两种基本的情形:饿汉式和懒汉式

直接在静态区初始化 instance,然后通过 get 方法返回,这样这个类每次直接先生成一个对象,好像好久没吃饭的饿汉子,急着吃饭一样,急切的 new 对象,这叫做饿汉式单例类。或者是在 get 方法中才 new instance,然后返回这个对象,和懒汉字一样,不主动做事,需要调用 get 方法的时候,才 new 对象,这就叫做懒汉式单例类。

如下是懒汉式单例类

 1 //单例模式示例
 2 class Singleton
 3 {
 4 public:
 5     static Singleton * getInstance()
 6     {
 7         if (instance == NULL) {
 8             instance = new Singleton();
 9         }
10
11         return instance;
12     }
13
14 private:
15     //私有的构造函数,防止外人私自调用
16     Singleton()
17     {
18         cout << "实例化了" << count << "个对象!" << endl;
19         count++;
20     }
21     //声明一个静态实例,静态函数只能使用静态的数据成员。整个类中静态成员只有一个实例,通常在实现源文件中被初始化。
22     static Singleton *instance;
23     //记录实例化的对象
24     int count = 1;
25 };
26
27 Singleton * Singleton::instance = NULL;
28
29 int main(void)
30 {
31     Singleton::getInstance();
32     Singleton::getInstance();
33     Singleton::getInstance();
34     Singleton::getInstance();
35
36     return 0;
37 }

实例化了1个对象!

Program ended with exit code: 0

小结:

懒汉式单例模式是用时间换取控件,饿汉式单例模式,是用空间换取时间。

继续分析,考虑多线程下的懒汉式单例模式

上述代码在单线程的情况下,运行正常,但是遇到了多线程就出问题,假设有两个线程同时运行了这个单例类,同时运行到了判断 if 语句,并且当时,instance 实例确实没有被初始化呢,那么两个线程都会去运行并创建实例,此时就不满足单例类的要求了。那么我们需要写上线程同步的功能。

 1 //考虑到多线程情形下的单例模式
 2 class Singleton
 3 {
 4 public:
 5     //get 方法
 6     static Singleton * getInstance(){
 7         //联系互斥信号量机制,给代码加锁
 8         lock();
 9         //判断 null
10         if (NULL == instance) {
11             //判断类没有生成对象,才实例化对象,否则不再实例化
12             instance = new Singleton();
13         }
14         //使用完毕,解锁
15         unlock();
16         //返回一个实例化的对象
17         return instance;
18     }
19 private:
20     //声明对象计数器
21     int count = 0;
22     //声明一个静态的实例
23     static Singleton *instance;
24     //私有构造函数
25     Singleton(){
26         count++;
27         cout << "实例化了" << count << "个对象!" << endl;
28     }
29 };
30 //初始化 instance
31 Singleton * Singleton::instance = NULL;

此时,还是有 ab 两个线程来运行这个单例类,由于在同一时刻,只有一个线程能拿到同步锁(互斥信号量机制),a 拿到了同步锁,b 只能等待,如果 a发现实例还没创建,a 就会创建一个实例,创建完毕,a 释放同步锁,然后 b 才能拿到同步锁,继续运行接下来的代码,b 发现 a 线程运行的时候,已经生成了一个实例,b 线程就不会重复创建实例了,这样就保证了我们在多线程环境中只能得到一个实例。

继续分析多线程下的懒汉式单例模式

代码中,每次 get 方法中,得到 instance,都要判断是否为空,且判断是否为空之前,都要先加同步锁,如果线程很多的时候,就要先等待加了同步锁的线程运行完毕,才能继续判断余下的线程,这样就会造成大量线程的阻塞,且加锁是个非常消耗时间的过程,应该尽量避免(除非很有必要的时候)。可行的办法是,双重判断方法。

因为,只是在实例还没有创建的时候,需要加锁判断,保证每次只有一个线程创建实例,而当实例已经创建之后,其实就不需要加锁操作了。

双重判断的线程安全的懒汉式单例模式

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //先判断一次 null,只有 null 的时候需要加锁,其他的时候,其实不需要加锁
 7         if (NULL == instance) {
 8             //联系互斥信号量机制,给代码加锁
 9             lock();
10             //然后再次判断 null
11             if (NULL == instance) {
12                 //判断类没有生成对象,才实例化对象,否则不再实例化
13                 instance = new Singleton();
14             }
15             //使用完毕,解锁
16             unlock();
17         }
18                 //返回一个实例化的对象
19         return instance;
20     }
21 private:
22     //声明对象计数器
23     int count = 0;
24     //声明一个静态的实例
25     static Singleton *instance;
26     //私有构造函数
27     Singleton(){
28         count++;
29         cout << "实例化了" << count << "个对象!" << endl;
30     }
31 };
32 //初始化 instance
33 Singleton * Singleton::instance = NULL;

这样的双重检测机制,提高了单例模式在多线程下的效率,因为这样的代码,只需要在第一次创建实例的时候,需要加锁,其他的时候,线程无需排队等待加锁之后,再去判断了,比较高效。

再看饿汉式的单例模式,之前看了懒汉式的单例类,是线程不安全的,通过加锁(双重锁),实现线程安全

回忆饿汉式单例类:直接在静态区初始化 instance,然后通过 get 方法返回,这样这个类每次直接先生成一个对象,好像好久没吃饭的饿汉子,急着吃饭一样,急切的 new 对象,这叫做饿汉式单例类。

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //返回一个实例化的对象
 7         return instance;
 8     }
 9 private:
10     //声明一个静态的实例
11     static Singleton *instance;
12     //私有构造函数
13     Singleton(){
14
15     }
16 };
17 //每次先直接实例化instance,get 方法直接返回这个实例
18 Singleton * Singleton::instance = new Singleton();

注意:静态初始化实例可以保证线程安全,因为静态实例初始化在程序开始时进入主函数之前,就由主线程以单线程方式完成了初始化!饿汉式的单例类,也就是静态初始化实例保证其线程安全性,故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。

继续看单例模式

上面的单例模式没有 destory() 方法,也就是说,貌似上面的单例类没有主动析构这个唯一实例!然而这就导致了一个问题,在程序结束之后,该单例对象没有delete,导致内存泄露!下面是一些大神的方法:一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。

我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。利用这些特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的Garbage类:

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //判断单例否
 7         if (NULL == instance) {
 8             instance = new Singleton();
 9         }
10         //返回一个实例化的对象
11         return instance;
12     }
13     //c++ 嵌套的内部类,作用是删除单例类对象,Garbage被定义为Singleton的内嵌类,以防该类被在其他地方滥用。
14     class Garbage
15     {
16     public:
17         ~Garbage(){
18             if (Singleton::instance != NULL) {
19                 cout << "单例类的唯一实例被析构了" << endl;
20                 delete Singleton::instance;
21             }
22         }
23     };
24
25 private:
26     //单例类中声明一个触发垃圾回收类的静态成员变量,它的唯一工作就是在析构函数中删除单例类的实例,利用程序在结束时析构全局变量的特性,选择最终的释放时机;
27     static Garbage garbage;
28     //声明一个静态的实例
29     static Singleton *instance;
30     //单例类的私有构造函数
31     Singleton(){
32         cout << "调用了单例类的构造函数" << endl;
33     }
34     //单例类的私有析构函数
35     ~Singleton(){
36         cout << "调用了单例类的析构函数" << endl;
37     }
38 };
39 //初始化内部的静态变量,目睹是启动删除的析构函数,如果不初始化,就不会被析构
40 //内部类可以访问外部类的私有成员,外部类不能访问内部类的私有成员!
41 Singleton::Garbage Singleton::garbage;
42 //初始化instance为 null
43 Singleton * Singleton::instance = NULL;
44
45 int main(void)
46 {
47     Singleton *a = Singleton::getInstance();
48     Singleton *b = Singleton::getInstance();
49     Singleton *c = Singleton::getInstance();
50
51     if (a == b) {
52         cout << "a = b" << endl;
53     }
54
55     return 0;
56 }

调用了单例类的构造函数

a = b

单例类的唯一实例被析构了

调用了单例类的析构函数

Program ended with exit code: 0

类Garbage被定义为Singleton的内嵌类,以防该类在其他地方滥用,程序运行结束时,系统会调用Singleton的静态成员garbage的析构函数,该析构函数会删除单例的唯一实例,使用这种方法释放单例对象有以下特征:

1、在单例类内部定义专有的嵌套类;

2、在单例类内定义私有的专门用于释放的静态成员;

3、利用程序在结束时析构全局变量的特性,选择最终的释放时机;

4、使用单例的代码不需要任何操作,不必关心对象的释放。

其实,继续想单例类的实现,有的人会这样做:

在程序结束时调一个专门的方法,这个方法里判断实例对象是否为 null,如果不为 null,就对返回的指针掉用delete操作。这样做可以实现删除单例的功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。不推荐直接的删除方法。

继续查看单例模式:单例模式在实际开发过程中是很有用的,单例模式的特征总结:

1、一个类只有一个实例

2、提供一个全局访问点

3、禁止拷贝

逐个分析:

1、实现只有一个实例,需要做的事情:将构造函数声明为私有

2、提供一个全局访问点,需要做的事情:类中创建静态成员和静态成员方法

3、禁止拷贝:把拷贝构造函数声明为私有,并且不提供实现,将赋值运算符声明为私有,防止对象的赋值

完整的单例类实现代码如下:

class Singleton
{
public://get 方法static Singleton * getInstance(){if (NULL == instance) {lock();//判断单例否if (NULL == instance) {instance = new Singleton();}unlock();}//返回一个实例化的对象return instance;}//c++ 嵌套的内部类,作用是删除单例类对象,Garbage被定义为Singleton的私有内嵌类,以防该类被在其他地方滥用。class Garbage{public:~Garbage(){if (Singleton::instance != NULL) {cout << "单例类的唯一实例被析构了" << endl;delete Singleton::instance;}}};private://单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例,利用程序在结束时析构全局变量的特性,选择最终的释放时机;static Garbage garbage;//声明一个静态的实例static Singleton *instance;//单例类的私有构造函数
    Singleton(){cout << "调用了单例类的构造函数" << endl;}//单例类的私有析构函数~Singleton(){cout << "调用了单例类的析构函数" << endl;}//把拷贝构造函数声明为私有,就可以禁止外人拷贝对象,也不用实现它,声明私有即可Singleton(const Singleton &copy);//把赋值运算符重载为私有的,防止对象之间的赋值操作Singleton & operator=(const Singleton &other);
};
//初始化内部似有泪的静态变量,目睹是启动删除的析构函数,如果不初始化,就不会被析构
//内部类可以访问外部类的私有成员,外部类不能访问内部类的私有成员!
Singleton::Garbage Singleton::garbage;
//初始化instance为 null
Singleton * Singleton::instance = NULL;int main(void)
{Singleton *a = Singleton::getInstance();Singleton *b = Singleton::getInstance();Singleton *c = Singleton::getInstance();if (a == b) {cout << "a = b" << endl;}return 0;
}

单例类de测试,两个方法:

1、实例化多个对象,看调用了几次构造函数,如果只调用一次,说明只创建一个实例

2、单步跟踪,查看对象的地址,是否一样,一样则为一个对象

软件开发常用设计模式—单例模式总结(c++版)相关推荐

  1. 软件开发常用名词中英文对照

    软件开发常用名词中英文对照 词汇 释义 abort 中止 abstract class 抽象类 accelerator 快捷键 accelerator mapping 快捷键映射 accelerato ...

  2. 软件开发常用词汇中英文对照

    按Ctrl + F自行查找 最后更新时间: 01/11/2019 15:17 软件名称相关发音读法: Ajax [ey-jaks], ['eidʒæks] 发音地址参照: https://www.di ...

  3. C#(Net)软件开发常用工具汇总,提高你的开发效率

    C#(Net)软件开发常用工具汇总,提高你的开发效率 写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用技术文章. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是 ...

  4. 程序员必备的1000个单词(分享软件开发常用词汇表)

    原文地址:软件开发常用词汇表 - 方包博客 - java|python|前端开发|运维|电商|ui设计 软件开发常用词汇,编程常用词汇,程序员常用到的单词. 软件开发常用词汇表 本词汇表汇集了软件开发 ...

  5. 【转】软件开发常用术语

    软件开发技术常用术语 A.I. 人工智能 A2A integration A2A整合 abstract 抽象的 abstract base class (ABC)抽象基类 abstract class ...

  6. 20个软件开发常用设计文档大全下载

    搜集了一些软件开发的常用文档,分享给大家 总下载地址: http://files.cnblogs.com/hsapphire/20%E4%B8%AA%E8%BD%AF%E4%BB%B6%E5%BC%8 ...

  7. 软件开发常用工具汇总百度云网盘

    开发常用软件 全部百度云资源 直接可以下载使用 产品原型设计工具:Axure 添加链接描述密码:7iq3 win终端工具cmder cmder 密码:gaf0 Navicat Navicat密码:l4 ...

  8. potian的软件开发常用工具箱

    准备工具 一.白板和白板笔 需求阶段和客户讨论问题时分析.设计.客户在这里自由交流大家对问题的看法 在项目分析和设计阶段用来进行头脑风暴,是设计的最重要的工具.白板上画的可以是UML图,但也可以是项目 ...

  9. 软件开发常用术语检索语

    SE      Software Engineering 软件工程 BURS    Business and User Requirement Specification  业务和用户需求规格说明 C ...

  10. 猿创征文|【开发工具-我打辅助的】2022软件开发常用辅助工具

    总览 常说工欲善其事必先利其器,人力终归有限,而软件开发又是一个相当耗费时间和经历的过程,而简捷的开发辅助工具则能够大大提高我们软件工程师的开发效率. 必关注网站 国家信息安全漏洞库 官网:https ...

最新文章

  1. 从化工行业转到IT行业
  2. 怎么用php制作会员注册表单,帝国CMS前台会员登陆表单的制作教程
  3. 不动的,稳定的,有序的东西就是低熵,相反的就是动的,不稳定的
  4. jdbc oracle添加数据库连接,JDBC与Oracle数据库连接最常用方法
  5. 三分钟带你掌握 CSS3 的新属性
  6. 几种人类设计的永动机,最后一个彻底服了!| 今日最佳
  7. python简单语法_python的基本语法(一)
  8. could not find driver和PDO drivers = no value
  9. java switch case怎么判断范围_java小白从入门到精通(基础二)
  10. asp.net页面加载时间计算
  11. java 获取视频时长
  12. 以太网未识别的网络win10_Win10以太网未识别的网络怎么办
  13. 机器学习实战——xgboost股票close预测
  14. nginx反向代理 负载均衡
  15. Android Software
  16. ASP.Net免费发送短信
  17. 5G通信设备安装,不仅是技术活,更是一门艺术活!(干货图文大全)
  18. TiDB 在零氪科技(LinkDoc)大数据医疗系统的实践
  19. ARM Cortex-M3
  20. JAVA安卓4.4.4_Android源码4.4.4_r1下载和编译

热门文章

  1. 纵坐标是横的还是竖的_通感在书法“竖”中的运用,详解竖的知识和技法(硬笔为例)...
  2. mysql sleep进程 java_请教java更新mysql,更新进程sleep
  3. ideal如何用标签输出_实战|如何使用JavaScript访问设备前后摄像头
  4. php上传文件 按日期文件夹,fckeditor上传文件按日期存放及重命名方法
  5. java对xml文件的读写_用java实现对xml文件的读写
  6. 第二十三章:触发器和行为(九)
  7. 编辑请求内容 Charles
  8. web测试实践作业进度报告三
  9. 《MapReduce 2.0源码分析与编程实战》一1.2 大数据分析时代
  10. 阿里云 ECS Ubuntu 14.04 无法访问之磁盘 IO 跑满问题排查