软件开发常用设计模式—单例模式总结(c++版)
单例模式:就是只有一个实例。
singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点。这个类称为单例类。如一个工程中,数据库访问对象只有一个,电脑的鼠标只能连接一个,操作系统只能有一个窗口管理器等,这时可以考虑使用单例模式。
众所周知,c++中,类对象被创建时,编译系统为对象分配内存空间,并自动调用构造函数,由构造函数完成成员的初始化工作,也就是说使用构造函数来初始化对象。
1、那么我们需要把构造函数设置为私有的 private,这样可以禁止别人使用构造函数创建其他的实例。
2、又单例类要一直向系统提供这个实例,那么,需要声明它为静态的实例成员,在需要的时候,才创建该实例。
3、且应该把这个静态成员设置为 null,在一个public 的方法里去判断,只有在静态实例成员为 null,也就是没有被初始化的时候,才去初始化它,且只被初始化一次。
通常我们可以让一个全局变量使得一个对象被访问,但它不能阻止你实例化多个对象。如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响。
一个最好的办法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法,单例模式比全局对象好还包括,单例类可以继承。
单例模式又分为两种基本的情形:饿汉式和懒汉式
直接在静态区初始化 instance,然后通过 get 方法返回,这样这个类每次直接先生成一个对象,好像好久没吃饭的饿汉子,急着吃饭一样,急切的 new 对象,这叫做饿汉式单例类。或者是在 get 方法中才 new instance,然后返回这个对象,和懒汉字一样,不主动做事,需要调用 get 方法的时候,才 new 对象,这就叫做懒汉式单例类。
如下是懒汉式单例类
//单例模式示例
class Singleton
{
public:
static Singleton * getInstance()
{
if (instance == NULL) {
instance = new Singleton();
} return instance;
} private:
//私有的构造函数,防止外人私自调用
Singleton()
{
cout << "实例化了" << count << "个对象!" << endl;
count++;
}
//声明一个静态实例,静态函数只能使用静态的数据成员。整个类中静态成员只有一个实例,通常在实现源文件中被初始化。
static Singleton *instance;
//记录实例化的对象
int count = ;
}; Singleton * Singleton::instance = NULL; int main(void)
{
Singleton::getInstance();
Singleton::getInstance();
Singleton::getInstance();
Singleton::getInstance(); return ;
}
实例化了1个对象!
Program ended with exit code: 0
小结:
懒汉式单例模式是用时间换取控件,饿汉式单例模式,是用空间换取时间。
继续分析,考虑多线程下的懒汉式单例模式
上述代码在单线程的情况下,运行正常,但是遇到了多线程就出问题,假设有两个线程同时运行了这个单例类,同时运行到了判断 if 语句,并且当时,instance 实例确实没有被初始化呢,那么两个线程都会去运行并创建实例,此时就不满足单例类的要求了。那么我们需要写上线程同步的功能。
//考虑到多线程情形下的单例模式
class Singleton
{
public:
//get 方法
static Singleton * getInstance(){
//联系互斥信号量机制,给代码加锁
lock();
//判断 null
if (NULL == instance) {
//判断类没有生成对象,才实例化对象,否则不再实例化
instance = new Singleton();
}
//使用完毕,解锁
unlock();
//返回一个实例化的对象
return instance;
}
private:
//声明对象计数器
int count = ;
//声明一个静态的实例
static Singleton *instance;
//私有构造函数
Singleton(){
count++;
cout << "实例化了" << count << "个对象!" << endl;
}
};
//初始化 instance
Singleton * Singleton::instance = NULL;
此时,还是有 ab 两个线程来运行这个单例类,由于在同一时刻,只有一个线程能拿到同步锁(互斥信号量机制),a 拿到了同步锁,b 只能等待,如果 a发现实例还没创建,a 就会创建一个实例,创建完毕,a 释放同步锁,然后 b 才能拿到同步锁,继续运行接下来的代码,b 发现 a 线程运行的时候,已经生成了一个实例,b 线程就不会重复创建实例了,这样就保证了我们在多线程环境中只能得到一个实例。
继续分析多线程下的懒汉式单例模式
代码中,每次 get 方法中,得到 instance,都要判断是否为空,且判断是否为空之前,都要先加同步锁,如果线程很多的时候,就要先等待加了同步锁的线程运行完毕,才能继续判断余下的线程,这样就会造成大量线程的阻塞,且加锁是个非常消耗时间的过程,应该尽量避免(除非很有必要的时候)。可行的办法是,双重判断方法。
因为,只是在实例还没有创建的时候,需要加锁判断,保证每次只有一个线程创建实例,而当实例已经创建之后,其实就不需要加锁操作了。
双重判断的线程安全的懒汉式单例模式
class Singleton
{
public:
//get 方法
static Singleton * getInstance(){
//先判断一次 null,只有 null 的时候需要加锁,其他的时候,其实不需要加锁
if (NULL == instance) {
//联系互斥信号量机制,给代码加锁
lock();
//然后再次判断 null
if (NULL == instance) {
//判断类没有生成对象,才实例化对象,否则不再实例化
instance = new Singleton();
}
//使用完毕,解锁
unlock();
}
//返回一个实例化的对象
return instance;
}
private:
//声明对象计数器
int count = ;
//声明一个静态的实例
static Singleton *instance;
//私有构造函数
Singleton(){
count++;
cout << "实例化了" << count << "个对象!" << endl;
}
};
//初始化 instance
Singleton * Singleton::instance = NULL;
这样的双重检测机制,提高了单例模式在多线程下的效率,因为这样的代码,只需要在第一次创建实例的时候,需要加锁,其他的时候,线程无需排队等待加锁之后,再去判断了,比较高效。
再看饿汉式的单例模式,之前看了懒汉式的单例类,是线程不安全的,通过加锁(双重锁),实现线程安全
回忆饿汉式单例类:直接在静态区初始化 instance,然后通过 get 方法返回,这样这个类每次直接先生成一个对象,好像好久没吃饭的饿汉子,急着吃饭一样,急切的 new 对象,这叫做饿汉式单例类。
class Singleton
{
public:
//get 方法
static Singleton * getInstance(){
//返回一个实例化的对象
return instance;
}
private:
//声明一个静态的实例
static Singleton *instance;
//私有构造函数
Singleton(){ }
};
//每次先直接实例化instance,get 方法直接返回这个实例
Singleton * Singleton::instance = new Singleton();
注意:静态初始化实例可以保证线程安全,因为静态实例初始化在程序开始时进入主函数之前,就由主线程以单线程方式完成了初始化!饿汉式的单例类,也就是静态初始化实例保证其线程安全性,故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。
继续看单例模式
上面的单例模式没有 destory() 方法,也就是说,貌似上面的单例类没有主动析构这个唯一实例!然而这就导致了一个问题,在程序结束之后,该单例对象没有delete,导致内存泄露!下面是一些大神的方法:一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。利用这些特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的Garbage类:
class Singleton
{
public:
//get 方法
static Singleton * getInstance(){
//判断单例否
if (NULL == instance) {
instance = new Singleton();
}
//返回一个实例化的对象
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::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 ;
}
调用了单例类的构造函数
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 ©);
//把赋值运算符重载为私有的,防止对象之间的赋值操作
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 ;
}
单例类de测试,两个方法:
1、实例化多个对象,看调用了几次构造函数,如果只调用一次,说明只创建一个实例
2、单步跟踪,查看对象的地址,是否一样,一样则为一个对象
欢迎关注
dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!
软件开发常用设计模式—单例模式总结(c++版)的更多相关文章
- IOS开发常用设计模式
IOS开发常用设计模式 说起设计模式,感觉自己把握不了笔头,所以单拿出iOS开发中的几种常用设计模式谈一下. 单例模式(Singleton) 概念:整个应用或系统只能有该类的一个实例 在iOS开发我们 ...
- C#(Net)软件开发常用工具汇总,提高你的开发效率
C#(Net)软件开发常用工具汇总,提高你的开发效率 写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用技术文章. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是 ...
- DevExpress控件开发常用要点(项目总结版)
使用DevExpress控件来做项目开发已经有很长一段时间了,在摸索开发到客户苛刻要求的过程中,其中碰到过很多问题需要解决的,随着一个个问题的解决,也留下很多对DevExpress控件的使用经验及教训 ...
- Linux软件开发常用的软件包(持续更新中)
下面是Linux开发常用的软件包: 软件包的名称 作用描述 安装方式 build-essential sudo apt-get install build-essential policycore ...
- IT软件开发常用英语词汇
Aabstract 抽象的abstract base class (ABC)抽象基类abstract class 抽象类abstraction 抽象.抽象物.抽象性access 存取.访问access ...
- 开发必看 | iOS开发常用设计模式!
ios开发学习中,经常弄不清楚ios的开发模式,今天我们就来进行简单的总结和探讨~(一)代理模式 应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现.优势:解耦合敏捷原则: ...
- 软件开发常用快捷键 & 命令总结
HTML + JavaScript + CSS 1. 在web开发经常会遇到如: 这样的字符.它其实是Html将一些特殊字符(Html语法字符)的一种表达方式. 下面列举几个常用字符: 空 ...
- 软件开发常用的linux命令心得(ubuntu为例)
软件开发过程中难免要经常对主机进行配置或者部署等操作,想到一些就写一些了,以后再更新 解压命令: a.如果是tar文件,则直接用 “tar zxvf 文件名”: b.如果是zip文件,用 “unzip ...
- 软件开发常用的linux命令心得
软件开发过程中难免要经常对主机进行配置或者部署等操作,想到一些就写一些了,以后再更新 解压命令: a.如果是tar文件,则直接用 “tar zxvf 文件名”: b.如果是zip文件,用 “unzip ...
随机推荐
- Daily Scrum02 12.16
鉴于近期数据库大作业提交到了关键时期,大家的时间都十分的紧张,因而,我们决定全团队成员交替作业,不在每日每个成员都分配任务,而只需要每个成员保证每两天一次的工作量,以此方式给大家腾出去完成自己的事情. ...
- $.each ---- 跳出当前的循环
有些朋友可能会以为在jquery跳出循环可以直接使用continue和break了,但是使用之后没有效果,因为在jquery中没有这两条命令.后来上网查了下,得到了结果:return false;—— ...
- html websocket
from:http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/ websocket 规范升级过,在该链接的文章内未提及,后面 ...
- TCP和UDP的区别
(1)TCP是面向连接的传输控制协议,而UDP提供了无连接的数据报服务:(2)TCP具有高可靠性,确保传输数据的正确性,不出现丢失或乱序:UDP在传输数据前不建立连接,不对数据报进行检查与修改,无须等 ...
- SDOI 2016 游戏
树链剖分 线段树维护区间最小值,区间最大值 更新,对于每一个区间,找到当前区间的最小值的最大值,和要更新的值比较,如果比最大值还大,则此数对于以后的询问无任何贡献,直接返回即可,若有贡献,则一直递归到 ...
- 一图搞定【实战Java高并发程序设计】
来了解下java并发的技术点吧.这里面包括了并发级别.算法.定律,还有开发包.在过去单核CPU时代,单任务在一个时间点只能执行单一程序,随着多核CPU的发展,并行程序开发就显得尤为重要.这本书主要介绍 ...
- WebView解析
WebView解析 WebView是一个基于Webkit的,相当于内置浏览器的强大功能的组件,WebView的使用这么分四步说明:添加组件,加载资源,属性设置,辅助功能. 一.WebView的添加 ...
- SQL Server恢复软件SysTools SQL Recovery/SysTools SQL Server Recovery Manager
SQL Server恢复软件SysTools SQL Recovery/SysTools SQL Server Recovery Manager http://www.systoolsgroup.co ...
- C#实现DNS解析服务和智能DNS服务
C#实现DNS解析服务有一个开源项目ARSoft.Tools.Net, ARSoft.Tools.Net是一个非常强大的开源DNS控件库,包含.Net SPF validation, SenderID ...
- Javascript判断两个日期是否相等
大家一定遇到过这样的情况,有两个日期对象,然后需要判断他们是否相等. 例如: var date1 = new Date("2013-11-29"); var date2 = new ...