剑指offer (2) c++实现singleton模式

转自:http://www.jellythink.com/archives/82

问题描述


现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能;在实际开发过程中,会专门有一个日志模块,负责写日志,由于在系统的任何地方,我们都有可能要调用日志模块中的函数,进行写日志。那么,如何构造一个日志模块的实例呢?难道,每次new一个日志模块实例,写完日志,再delete,不要告诉我你是这么干的。在C++中,可以构造一个日志模块的全局变量,那么在任何地方就都可以用了,是的,不错。但是,我所在的开发部门的C++编码规范是参照Google的编码规范的。

全局变量在项目中是能不用就不用的,它是一个定时炸弹,是一个不安全隐患,特别是在多线程程序中,会有很多的不可预测性;同时,使用全局变量,也不符合面向对象的封装原则,所以,在纯面向对象的语言Java和C#中,就没有纯粹的全局变量。那么,如何完美的解决这个日志问题,就需要引入设计模式中的单例模式。

单例模式

何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。意思很明白,使用UML类图表示如下。

UML类图

代码实现

单例模式,单从UML类图上来说,就一个类,没有错综复杂的关系。但是,在实际项目中,使用代码实现时,还是需要考虑很多方面的。

实现一:


 1 /*
2 ** FileName : SingletonPatternDemo1
3 ** Author : Jelly Young
4 ** Date : 2013/11/20
5 ** Description : More information, please go to http://www.jellythink.com
6 */
7
8 #include <iostream>
9 using namespace std;
10
11 class Singleton
12 {
13 public:
14 static Singleton *GetInstance()
15 {
16 if (m_Instance == NULL )
17 {
18 m_Instance = new Singleton ();
19 }
20 return m_Instance;
21 }
22
23 static void DestoryInstance()
24 {
25 if (m_Instance != NULL )
26 {
27 delete m_Instance;
28 m_Instance = NULL ;
29 }
30 }
31
32 // This is just a operation example
33 int GetTest()
34 {
35 return m_Test;
36 }
37
38 private:
39 Singleton(){ m_Test = 10; }
40 static Singleton *m_Instance;
41 int m_Test;
42 };
43
44 Singleton *Singleton ::m_Instance = NULL;
45
46 int main(int argc , char *argv [])
47 {
48 Singleton *singletonObj = Singleton ::GetInstance();
49 cout<<singletonObj->GetTest()<<endl;
50
51 Singleton ::DestoryInstance();
52 return 0;
53 }

这是最简单,也是最普遍的实现方式,也是现在网上各个博客中记述的实现方式,但是,这种实现方式,有很多问题,比如:没有考虑到多线程的问题,在多线程的情况下,就可能创建多个Singleton实例,以下版本是改善的版本。

实现二:


 1 /*
2 ** FileName : SingletonPatternDemo2
3 ** Author : Jelly Young
4 ** Date : 2013/11/20
5 ** Description : More information, please go to http://www.jellythink.com
6 */
7

8 #include <iostream>
9 using namespace std;
10
11 class Singleton
12 {
13 public:
14 static Singleton *GetInstance()
15 {
16 if (m_Instance == NULL )
17 {
18 Lock(); // C++没有直接的Lock操作,请使用其它库的Lock,比如Boost,此处仅为了说明
19 if (m_Instance == NULL )
20 {
21 m_Instance = new Singleton ();
22 }
23 UnLock(); // C++没有直接的Lock操作,请使用其它库的Lock,比如Boost,此处仅为了说明
24 }
25 return m_Instance;
26 }
27
28 static void DestoryInstance()
29 {
30 if (m_Instance != NULL )
31 {
32 delete m_Instance;
33 m_Instance = NULL ;
34 }
35 }
36
37 int GetTest()
38 {
39 return m_Test;
40 }
41
42 private:
43 Singleton(){ m_Test = 0; }
44 static Singleton *m_Instance;
45 int m_Test;
46 };
47
48 Singleton *Singleton ::m_Instance = NULL;
49
50 int main(int argc , char *argv [])
51 {
52 Singleton *singletonObj = Singleton ::GetInstance();
53 cout<<singletonObj->GetTest()<<endl;
54 Singleton ::DestoryInstance();
55
56 return 0;
57 }

此处进行了两次m_Instance ==
NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。但是,这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。

实现三:


 1 /*
2 ** FileName : SingletonPatternDemo3
3 ** Author : Jelly Young
4 ** Date : 2013/11/20
5 ** Description : More information, please go to http://www.jellythink.com
6 */
7
8 #include <iostream>
9 using namespace std;
10
11 class Singleton
12 {
13 public:
14 static Singleton *GetInstance()
15 {
16 return const_cast <Singleton *>(m_Instance);
17 }
18
19 static void DestoryInstance()
20 {
21 if (m_Instance != NULL )
22 {
23 delete m_Instance;
24 m_Instance = NULL ;
25 }
26 }
27
28 int GetTest()
29 {
30 return m_Test;
31 }
32
33 private:
34 Singleton(){ m_Test = 10; }
35 static const Singleton *m_Instance;
36 int m_Test;
37 };
38
39 const Singleton *Singleton ::m_Instance = new Singleton();
40
41 int main(int argc , char *argv [])
42 {
43 Singleton *singletonObj = Singleton ::GetInstance();
44 cout<<singletonObj->GetTest()<<endl;
45 Singleton ::DestoryInstance();
46 }

因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。由于上述三种实现,都要考虑到实例的销毁,关于实例的销毁,待会在分析。由此,就出现了第四种实现方式:

实现四:


 1 /*
2 ** FileName : SingletonPatternDemo4
3 ** Author : Jelly Young
4 ** Date : 2013/11/20
5 ** Description : More information, please go to http://www.jellythink.com
6 */
7
8 #include <iostream>
9 using namespace std;
10
11 class Singleton
12 {
13 public:
14 static Singleton *GetInstance()
15 {
16 static Singleton m_Instance;
17 return &m_Instance;
18 }
19
20 int GetTest()
21 {
22 return m_Test++;
23 }
24
25 private:
26 Singleton(){ m_Test = 10; };
27 int m_Test;
28 };
29
30 int main(int argc , char *argv [])
31 {
32 Singleton *singletonObj = Singleton ::GetInstance();
33 cout<<singletonObj->GetTest()<<endl;
34
35 singletonObj = Singleton ::GetInstance();
36 cout<<singletonObj->GetTest()<<endl;
37 }

以上就是四种主流的单例模式的实现方式,如果大家还有什么好的实现方式,希望大家能推荐给我。谢谢了。

实例销毁

在上述的四种方法中,除了第四种没有使用new操作符实例化对象以外,其余三种都使用了;我们一般的编程观念是,new操作是需要和delete操作进行匹配的;是的,这种观念是正确的。在上述的实现中,是添加了一个DestoryInstance的static函数,这也是最简单,最普通的处理方法了;但是,很多时候,我们是很容易忘记调用DestoryInstance函数,就像你忘记了调用delete操作一样。由于怕忘记delete操作,所以就有了智能指针;那么,在单例模型中,没有“智能单例”,该怎么办?怎么办?

那我先从实际的项目中说起吧,在实际项目中,特别是客户端开发,其实是不在乎这个实例的销毁的。因为,全局就这么一个变量,全局都要用,它的生命周期伴随着软件的生命周期,软件结束了,它也就自然而然的结束了,因为一个程序关闭之后,它会释放它占用的内存资源的,所以,也就没有所谓的内存泄漏了。但是,有以下情况,是必须需要进行实例销毁的:

  1. 在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放;

  2. 具有强迫症的程序员。

以上,就是我总结的两点。

虽然,在代码实现部分的第四种方法能满足第二个条件,但是无法满足第一个条件。好了,接下来,就介绍一种方法,这种方法也是我从网上学习而来的,代码实现如下:


 1 /*
2 ** FileName : SingletonPatternDemo5
3 ** Author : Jelly Young
4 ** Date : 2013/11/20
5 ** Description : More information, please go to http://www.jellythink.com
6 */
7
8 #include <iostream>
9 using namespace std;
10
11 class Singleton
12 {
13 public:
14 static Singleton *GetInstance()
15 {
16 return m_Instance;
17 }
18
19 int GetTest()
20 {
21 return m_Test;
22 }
23
24 private:
25 Singleton(){ m_Test = 10; }
26 static Singleton *m_Instance;
27 int m_Test;
28
29 // This is important
30 class GC
31 {
32 public :
33 ~GC()
34 {
35 // We can destory all the resouce here, eg:db connector, file handle and so on
36 if (m_Instance != NULL )
37 {
38 cout<< "Here is the test" <<endl;
39 delete m_Instance;
40 m_Instance = NULL ;
41 }
42 }
43 };
44 static GC gc;
45 };
46
47 Singleton *Singleton ::m_Instance = new Singleton();
48 Singleton ::GC Singleton ::gc;
49
50 int main(int argc , char *argv [])
51 {
52 Singleton *singletonObj = Singleton ::GetInstance();
53 cout<<singletonObj->GetTest()<<endl;
54
55 return 0;
56 }

在程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。那么这种实现方式的原理是什么呢?我剖析问题时,喜欢剖析到问题的根上去,绝不糊涂的停留在表面。由于程序在结束的时候,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,就像这些静态变量是全局变量一样。我们知道,静态变量和全局变量在内存中,都是存储在静态存储区的,所以在析构时,是同等对待的。

由于此处使用了一个内部GC类,而该类的作用就是用来释放资源,而这种使用技巧在C++中是广泛存在的,在后面的博客中,我会总结这一技巧,参见《C++中的RAII机制》

剑指offer (2) c++实现singleton模式,布布扣,bubuko.com

时间: 2024-10-01 02:19:30

剑指offer (2) c++实现singleton模式的相关文章

剑指Offer对答如流系列 - 实现Singleton模式

目录 面试题2:实现Singleton模式 一.懒汉式写法 二.饿汉式写法 三.枚举 面试题2:实现Singleton模式 题目:设计一个类,我们只能生成该类的一个实例. 由于设计模式在面向对象程序设计中起着举足轻重的作用,在面试过程中很多公司都喜欢问一些与设计模式相关的问题.在常用的模式中,Singleton是唯一一个能够用短短几十行代码完整实现的模式.因此,写一个Singleton的类型是一个很常见的面试题. 如果你看过我之前写的设计模式专栏,那么这道题思路你会很开阔. 单例模式的要点有三个

【剑指offer】面试题2:实现Singleto模式 java

题目:设计一个类,我们只能生成该类的一个实例. /*一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例. * 这样一来,只要不使用到这个类级内部类,那就不会创建对象实例, * 从而同时实现延迟加载和线程安全. * */ public class Singleton5 { private Singleton5() {//私有方法确保只创建一个实例 // TODO Auto-generated constructor stub } /** * 类级的内部类,也就是静态的成员式内部类

剑指 Offer 题目汇总索引

剑指 Offer 总目录:(共50道大题) 1. 赋值运算符函数(或应说复制拷贝函数问题) 2. 实现 Singleton 模式 (C#) 3.二维数组中的查找 4.替换空格               时间:O(n) 空间:O(1) 5.从尾到头打印链表 6. 重建二叉树          && 二叉树的各种遍历(BFS,DFS,DLR,LDR,LRD) 7.用两个栈实现队列 8.旋转数组的最小数字 9.斐波那契数列第 n 项        时间O(lgn) 10.一个整数的二进制表示中

剑指 Offer 题目索引

剑指 Offer 总目录:(共50道大题) 1. 赋值运算符函数(或应说复制拷贝函数问题) 2. 实现 Singleton 模式 (C#) 3.二维数组中的查找 4.替换空格               时间:O(n) 空间:O(1) 5.从尾到头打印链表 6. 重建二叉树          && 二叉树的各种遍历(BFS,DFS,DLR,LDR,LRD) 7.用两个栈实现队列 8.旋转数组的最小数字 9.斐波那契数列第 n 项        时间O(lgn) 10.一个整数的二进制表示中

【剑指Offer学习】【所有面试题汇总】

剑指Offer学习 剑指Offer这本书已经学习完了,从中也学习到了不少的东西,现在做一个总的目录,供自已和大家一起参考,学如逆水行舟,不进则退.只有不断地学习才能跟上时候,跟得上技术的潮流! 所有代码下载[https://github.com/Wang-Jun-Chao/coding-interviews] 目录 第01-10题 [剑指Offer学习][面试题02:实现Singleton 模式--七种实现方式] [剑指Offer学习][面试题03:二维数组中的查找] [剑指Offer学习][面

【剑指offer】【python】面试题2~5

使用python实现<剑指offer>面试题ヾ(?°∇°?)??,以此记录. 2_实现Singleton模式 题目:实现单例模式 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的一个类只有一个实例.即一个类只有一个对象实例. 原理:使用模块时,第一次导入会生成.pyc文件(存放相应目录下的__pycache__文件夹内),当第二次使用该模块时会直接加载pyc文件.所以,将函数.数据等定义在一个模块中即可完成一个单例对象.

【剑指offer】Q38:数字在数组中出现的次数

与折半查找是同一个模式,不同的是,在这里不在查找某个确定的值,而是查找确定值所在的上下边界. def getBounder(data, k, start, end, low_bound = False): if end < start : return -1 while start <= end: mid = ( start + end ) >> 1 if data[ mid ] > k: end = mid - 1 elif data[ mid ] < k: star

剑指Offer——知识点储备-数据库基础

剑指Offer--知识点储备-数据库基础 数据库 事务 事务的四个特性(ACID): ??原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability) - (1)原子性 整个事务中的所要操作要么全部提交成功,要么全部失败回滚. - (2)一致性 保证数据库中的数据操作之前和操作之后的一致性.(比如用户多个账户之间的转账,但是用户的总金额是不变的) - (3)隔离性 隔离性要求一个事务对数据库中数据的修改,在未提交完成前对于其它事务是

剑指offer之【表示数值的字符串】

题目: 表示数值的字符串 链接: https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 题目描述: 请实现一个函数用来判断字符串是否表示数值(包括整数和小数).例如,字符串"+100",&quo