C++中的单例模式

    单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。
       单例模式有许多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这样的代码显的很不优雅。 使用全局对象能够保证方便地访问实例,但是不能保证只声明一个对象——也就是说除了一个全局实例外,仍然能创建相同类的本地实例。
《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
       单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。

定义如下:

[cpp] view plaincopy
  1. class CSingleton  
  2. {  
  3. private:  
  4.     CSingleton()   //构造函数是私有的  
  5.     {  
  6.     }  
  7.     static CSingleton *m_pInstance;  
  8. public:  
  9.     static CSingleton * GetInstance()  
  10.     {  
  11.         if(m_pInstance == NULL)  //判断是否第一次调用  
  12.             m_pInstance = new CSingleton();  
  13.         return m_pInstance;  
  14.     }  
  15. };  
用户访问唯一实例的方法只有GetInstance()成员函数。如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的。GetInstance()使用 懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的 。这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针:
CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();
对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
 
单例类CSingleton有以下特征:
它有一个指向唯一实例的静态指针m_pInstance,并且是私有的;
它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;
它的构造函数是私有的,这样就不能从别处创建该类的实例。
大多数时候,这样的实现都不会出现问题。有经验的读者可能会问,m_pInstance指向的空间什么时候释放呢?更严重的问题是,该实例的析构函数什么时候执行?
如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。
可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。
一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):
[cpp] view plaincopy
  1. class CSingleton  
  2. {  
  3. private:  
  4.     CSingleton()  
  5.     {  
  6.     }  
  7.     static CSingleton *m_pInstance;  
  8.     class CGarbo   //它的唯一工作就是在析构函数中删除CSingleton的实例  
  9.     {  
  10.     public:  
  11.         ~CGarbo()  
  12.         {  
  13.             if(CSingleton::m_pInstance)  
  14.                 delete CSingleton::m_pInstance;  
  15.         }  
  16.     };  
  17.     static CGarbo Garbo;  //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数  
  18. public:  
  19.     static CSingleton * GetInstance()  
  20.     {  
  21.         if(m_pInstance == NULL)  //判断是否第一次调用  
  22.             m_pInstance = new CSingleton();  
  23.         return m_pInstance;  
  24.     }  
  25. };  
类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
在单例类内部定义专有的嵌套类;
在单例类内定义私有的专门用于释放的静态成员;
利用程序在结束时析构全局变量的特性,选择最终的释放时机;
使用单例的代码不需要任何操作,不必关心对象的释放。


进一步的讨论

但是添加一个类的静态对象,总是让人不太满意,所以有人用如下方法来重新实现单例和解决它相应的问题,代码如下:

[cpp] view plaincopy
  1. class CSingleton  
  2. {  
  3. private:  
  4.     CSingleton()   //构造函数是私有的  
  5.     {  
  6.     }  
  7. public:  
  8.     static CSingleton & GetInstance()  
  9.     {  
  10.         static CSingleton instance;   //局部静态变量  
  11.         return instance;  
  12.     }  
  13. };  
使用局部静态变量,非常强大的方法,完全实现了单例的特性,而且代码量更少,也不用担心单例销毁的问题。
但使用此种方法也会出现问题,当如下方法使用单例时问题来了,
Singleton singleton = Singleton :: GetInstance();
这么做就出现了一个类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:编译器会为类生成一个默认的构造函数,来支持类的拷贝。

最后没有办法,我们要禁止类拷贝和类赋值,禁止程序员用这种方式来使用单例,当时领导的意思是GetInstance()函数返回一个指针而不是返回一个引用,函数的代码改为如下:

[cpp] view plaincopy
  1. class CSingleton  
  2. {  
  3. private:  
  4.     CSingleton()   //构造函数是私有的  
  5.     {  
  6.     }  
  7. public:  
  8.     static CSingleton * GetInstance()  
  9.     {  
  10.         static CSingleton instance;   //局部静态变量  
  11.         return &instance;  
  12.     }  
  13. };  

但我总觉的不好,为什么不让编译器不这么干呢。这时我才想起可以显示的声明类拷贝的构造函数,和重载 = 操作符,新的单例类如下:

[cpp] view plaincopy
  1. class CSingleton  
  2. {  
  3. private:  
  4.     CSingleton()   //构造函数是私有的  
  5.     {  
  6.     }  
  7.     CSingleton(const CSingleton &);  
  8.     CSingleton & operator = (const CSingleton &);  
  9. public:  
  10.     static CSingleton & GetInstance()  
  11.     {  
  12.         static CSingleton instance;   //局部静态变量  
  13.         return instance;  
  14.     }  
  15. };  
关于Singleton(const Singleton);和 Singleton & operate = (const Singleton&);函数,需要声明成私有的,并且只声明不实现。这样,如果用上面的方式来使用单例时,不管是在友元类中还是其他的,编译器都是报错。
不知道这样的单例类是否还会有问题,但在程序中这样子使用已经基本没有问题了。


考虑到线程安全、异常安全,可以做以下扩展
[cpp] view plaincopy
  1. class Lock  
  2. {  
  3. private:         
  4.     CCriticalSection m_cs;  
  5. public:  
  6.     Lock(CCriticalSection  cs) : m_cs(cs)  
  7.     {  
  8.         m_cs.Lock();  
  9.     }  
  10.     ~Lock()  
  11.     {  
  12.         m_cs.Unlock();  
  13.     }  
  14. };  
  15.   
  16. class Singleton  
  17. {  
  18. private:  
  19.     Singleton();  
  20.     Singleton(const Singleton &);  
  21.     Singleton& operator = (const Singleton &);  
  22.   
  23. public:  
  24.     static Singleton *Instantialize();  
  25.     static Singleton *pInstance;  
  26.     static CCriticalSection cs;  
  27. };  
  28.   
  29. Singleton* Singleton::pInstance = 0;  
  30.   
  31. Singleton* Singleton::Instantialize()  
  32. {  
  33.     if(pInstance == NULL)  
  34.     {   //double check  
  35.         Lock lock(cs);           //用lock实现线程安全,用资源管理类,实现异常安全  
  36.         //使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的无论是因为异常抛出还是语句块结束。  
  37.         if(pInstance == NULL)  
  38.         {  
  39.             pInstance = new Singleton();  
  40.         }  
  41.     }  
  42.     return pInstance;  
  43. }  

之所以在Instantialize函数里面对pInstance 是否为空做了两次判断,因为该方法调用一次就产生了对象,pInstance == NULL 大部分情况下都为false,如果按照原来的方法,每次获取实例都需要加锁,效率太低。而改进的方法只需要在第一次 调用的时候加锁,可大大提高效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/351630.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 学习笔记(三)Spring boot 中的SSM

Spring boot 下的 Spring mvc Controller:即为Spring mvc的注解,处理http请求; RestController:Spring4后新增注解,是Controller与ResponseBody的组合注解,用于返回字符串或json数据; package c…

c mysql日期时间格式_mysql日期和时间类型

TIME 类型TIME 类型用于只需要时间信息的值,在存储时需要 3 个字节。格式为 HH:MM:SS。HH 表示小时,MM 表示分钟,SS 表示秒。TIME 类型的取值范围为 -838:59:59~838:59:59&#xff0…

MySQL问题汇总

1.#include <mysql.h>编译出错 在阅读TrinityCore时&#xff0c;发现了解决方法和解释&#xff1a; #ifdef _WIN32 // hack for broken mysql.h not including the correct winsock header for SOCKET definition, fixed in 5.7 #include <winsock2.h> #endif #inc…

c++中union的使用,看高手们如何解释的

union主要是共享内存&#xff0c;分配内存以其最大的结构或对象为大小&#xff0c;即sizeof最大的。在C/C程序的编写中&#xff0c;当多个基本数据类型或复合数据结构要占用同一片内存时&#xff0c;我们要使用联合体&#xff1b;当多种类型&#xff0c;多个对象&#xff0c;多…

ibm aix_IBM AIX:Java进程大小监视

ibm aix本文将为您提供有关如何计算在IBM AIX 5.3 OS上运行的Java VM进程的Java进程大小内存占用量的快速参考指南。 这是我关于该主题的原始文章的补充文章&#xff1a; 如何在AIX上监视Java本机内存 。 我强烈建议所有参与生产支持或AIX上部署Java应用程序开发的人员阅读此书…

路由的使用

1 路由的基本使用: url是个函数,有四个参数,第一个参数要传正则表达式,第二参数传函数内存地址,第三个参数传默认参数,第四个是路由的别名 url(r^article/aa.html$, views.test), -路由从上往下匹配,一旦匹配成功,后面就不继续匹配了 2 路由的无名分组 url(r^test2/(\d)/(\w)$,…

Ps2022版DR5插件扩展窗口不展示及未正确签署等问题修复

前言 最近在安装DR5的时候遇到了一些报错问题&#xff0c;翻看了几篇文章找了一些实质性的方案&#xff0c;亲测有效&#xff0c;有同样问题的小伙伴自己对号入座哈。 窗口扩展不显示问题 问题 很多人第一次安装DR5时会发现这个【窗口-扩展】是灰色的&#xff0c;且没有DR5…

centos 编译mysql5.6_centos下编译安装MySQL5.6

&#xff0c;虚拟机centos6.5mini网络适配器“桥接模式”继续上一次的Apache编译后&#xff0c;编译安装MySQL5.6MySQL5.6和以前的版本不同之处在于用cmake就行编译&#xff0c;先安装cmake#yum install cmake -y1、开始下载编译MySQL5.6&#xff0c;推荐镜像网站http://mirror…

Spring Boot和多模块项目–添加模块特定的属性文件

你好&#xff01; 在本文中&#xff0c;我将向您展示几种如何在Spring Boot项目中添加模块特定的属性文件的方法。 它将介绍使属性文件可识别配置文件的手动方法以及可识别配置文件的半自动方法。 我的Github帐户上发布了一个示例项目&#xff08; https://github.com/coders-…

修改hostname有几种方式?

1&#xff1a; hostname DB-Server --运行后立即生效&#xff08;新会话生效&#xff09;&#xff0c;但是在系统重启后会丢失所做的修改 2&#xff1a; echo DB-Server > /proc/sys/kernel/hostname --运行后立即生效&#xff08…

Java高阶语法---final

背景&#xff1a;听说final Java高阶语法是挺进BAT必经之路。 final: final关键字顾名思义就是最终不可改变的。 1、含义&#xff1a;final可以声明成员变量、方法、类和本地变量&#xff1b;一旦将引用声明为final&#xff0c;此引用将不再被改变&#xff0c;编译器会检查代码…

mysql 以 db 结尾_MySQL的高级部分

1. MySQL的事务(1)存储引擎的介绍介绍&#xff1a;当客户端发送一条SQL语句给服务器时&#xff0c;服务器端通过缓存、语法检查、校验通过之后&#xff0c;然后会通过调用底层的一些软件组织&#xff0c;去从数据库中查询数据&#xff0c;然后将查询到的结果集返回给客户端&…

VisualBasic 版 (精华区)

发信人: zkboy (小小鸟), 信区: VisualBasic 标 题: Re: 请教&#xff1a;如何在VB中实现16进制数或10进制数与2进制&#xfffd;发信站: BBS 水木清华站 (Thu Jun 15 19:39:37 2000)转一篇相关资料在编程中&#xff0c;我们经常使用二进制(binary)、八进制(octal)、十进…

Vim求生

[TOC] Vim 是从 vi 发展出来的一个文本编辑器。其代码补完、编译及错误跳转等方便编程的功能特别丰富&#xff0c;在程序员中被广泛使用。和 Emacs 并列成为类 Unix 系统用户最喜欢的编辑器。 —— 维基百科 很多接触 Vim 的程序员应该都有过这样的经历&#xff1a; 受某大 V 的…

activemq 内存_ActiveMQ:了解内存使用情况

activemq 内存正如最近的一些邮件列表电子邮件和Google返回的许多信息所表明的那样&#xff0c;ActiveMQ的SystemUsage尤其是MemoryUsage功能使一些人感到困惑。 我将尝试解释有关MemoryUsage的一些细节&#xff0c;这些细节可能有助于理解它的工作方式。 我将不介绍StoreUsage…

osg动态加载模型不显示_OSG仿真案例(8)——读取FBX格式文件并显示(无动画)...

包含的头#include #include #include #include #include #include #include #include #include using namespace std;所需要的类(结构体)struct AnimationManagerFinder : public osg::NodeVisitor{osg::ref_ptr<:basicanimationmanager> _am;AnimationManagerFinder(){o…

用微服务和容器替换旧版Java EE应用程序服务器

Lightbend最近对2000多个JVM开发人员进行了调查&#xff0c;结果刚刚发布。 开展该调查的目的是发现&#xff1a;发展趋势与IT基础设施趋势之间的相关性&#xff0c;处于数字化转型前沿的组织如何使他们的应用程序现代化以及当今对新兴开发人员技术最为关注的实际生产使用情况细…

点分治经典_动态点分治

HYSBZ_3730_震波 背景&#xff1a;这是接触的动态点分治第一题&#xff0c;开始不是很理解&#xff0c;看了很久&#xff0c;写了很久才理解了动态的动态所在。前置知识&#xff1a;静态点分治&#xff08;主要是容斥思想&#xff0c;一般看出来是点分治的话&#xff0c;想到怎…

python settings模块导入不了_无法导入设置“myproject.settings”(是否在sys.path上?):没有名为pinax的模块...

我正试图让皮纳克斯在网络派系工作&#xff0c;有这么多的问题。。。[Sun Feb 19 20:01:20 2012] [error] [client 127.0.0.1] mod_wsgi (pid22796): Exception occurred processing WSGI script /home/pawesome/webapps/qtsocial/myproject.wsgi.[Sun Feb 19 20:01:20 2012] […

VS2012 颜色配置成黑色

效果展示&#xff1a; 步骤&#xff1a; 1、工具->选项->环境->常规->颜色体验 选为深色 2、在http://studiostyl.es/schemes/son-of-obsidian中&#xff0c;下载Son of Obsidian字体设置&#xff0c;然后 工具->导入和导出设置->导入选定的环境设置->…