CPP design pattern Singleton
Singleton模式概念:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
然而真正实现起来却并非易事,甚至有些棘手。棘手之处在于删除问题,如何删除Singleton实例?谁来删除?何时删除?
在[DP]中,并未探讨有关析构和删除的问题,这是GoF的疏忽。此模式虽小,内涵却多,随着观察的深入,问题便突显出来。之后,John Vlissides(GoF之一)在Pattern hatching(1998)一书中探讨了这个问题。
和工厂模式等不同,Singleton对象需要自己对「所有权」进行管理,用户无权删除实例。
Singleton对象从创建到删除之间便是其生命期,然而,我们只知道创建时间,而不知其删除时间,也就无法管理所有权。
事实上就算我们不进行释放操作,程序在结束之时,操作系统也会将进程所用的内存完全释放,并不会产生内存泄漏。然而,若Singleton产生的实例在构造时申请过广泛的资源(通常是内核资源,如套接字,信号量,互斥体,事件),便会产生资源泄漏。解决办法就是在程序关闭时正确地删除Singleton对象,此时,删除时机便至关重要。
Singleton模式还有一个问题是多线程支持,不过C++11之后并非主要问题。
总结一下,Singleton模式虽然易于表达和理解,但却难以实现。关键在于三点:创建,删除,多线程。其中删除是最棘手的问题,能同时满足这三点的实现,便能适应几乎所有情况。
1 Singleton的唯一性
Singleton模式是一种经过改进的全局变量,重点集中于“产生和管理一个独立对象,并且不允许产生另外一个这样的对象”。
也就是说具有唯一性,因此一切能够再次产生同样对象的方式都应该被杜绝,比如:构造函数,拷贝构造,移动构造,赋值操作符。
[DP]中的定义如下:
1class Singleton2{3public:45 static Singleton* Instance() {6 if(!pInstance_) {7 pInstance_ = new Singleton;8 }9 return pInstance_;
10 }
11
12private:
13 Singleton();
14 Singleton(const Singleton&) = delete;
15 Singleton& operator=(const Singleton&) = delete;
16 Singleton(Singleton&&) = delete;
17 Singleton& operator=(Singleton&&) = delete;
18 ~Singleton();
19
20 static Singleton* pInstance_;
21};
22Singleton* Singleton::pInstance_ = 0;
这种方式的确满足了唯一性,用户除了从Instance()获取对象之外,别无他法。且用户无法意外删除对象,因为析构函数也被私有了(不过依然可以由返回的指针删除之,所以最好以引用返回)。
最大的问题在于此法只满足三大关键之一:创建,对于删除和多线程都不满足。
所以,在满足唯一性的前提下,Singleton的实现还应设法管理产生对象的生命期和多线程环境下的访问安全。
2 隐式析构的Singleton
1节的Singleton只有创建,没有删除,这可能会导致资源泄漏,一种解决方法是使用隐式析构。
C++的static成员变量的生命期伴随着整个程序,在程序关闭时由编译器负责销毁。于是我们可以创建一个static嵌套类,在其析构函数中释放singleton对象。
具体的实现如下:
1class singleton2{3public:4 static singleton& instance()5 {6 if(!pInstance_)7 {8 destroy_.create();9 std::cout << "create\n";
10 pInstance_ = new singleton;
11 }
12 return *pInstance_;
13 }
14
15private:
16 static singleton* pInstance_;
17
18 // embedded class
19 // implicit deconstructor
20 struct singleton_destroyer {
21 ~singleton_destroyer() {
22 if(pInstance_) {
23 std::cout << "singleton destroyed!\n";
24 delete pInstance_;
25 }
26 }
27 void create() {}
28 };
29 static singleton_destroyer destroy_;
30
31protected:
32 singleton() = default;
33 virtual ~singleton() {}
34 Singleton(const Singleton&) = delete;
35 Singleton& operator=(const Singleton&) = delete;
36 Singleton(Singleton&&) = delete;
37 Singleton& operator=(Singleton&&) = delete;
38};
39
40singleton* singleton::pInstance_ = nullptr;
41singleton::singleton_destroyer singleton::destroy_;
此时singleton将能够具有摧毁功能,此时再加入线程处理也并非难事,如何保证线程安全可以参见7.5节。
3 Meyers singleton
隐式析构的Singleton的确是一种不错的实现方式,然而C++实现Singleton最简单而有效的方法还是Meyers singleton,实现如下:
1class singleton2{3public:4 static singleton& instance() {5 static singleton obj;6 return obj; 7 }89private:
10 Singleton();
11 Singleton(const Singleton&) = delete;
12 Singleton& operator=(const Singleton&) = delete;
13 Singleton(Singleton&&) = delete;
14 Singleton& operator=(Singleton&&) = delete;
15 ~Singleton();
16};
此法并未使用动态分配和静态指针,而是借用了一个局部静态变量。
和上一个方法一样,该法也是到第一次执行时才会初始化对象(网上有些地方称之为懒汉式,然而在正统C++设计模式相关书籍中,皆未出现过此种叫法),它返回的是引用,所以用户无法对返回的对象进行delete操作(暴力转换法不算:))。此外,在C++11之后,这种方法也是线程安全的,所以对于大多情况下,这是最简单也是最有效的实作法,仅有两行代码。
** Meyers singleton 关键之处是让static对象定义在函数内部,变成局部static变量**
如果是把 static对象定义成 Singleton的私有static成员变量,然后getInstance()去返回这个成员即:
class Singleton {
public:static Singleton& getInstance() {return inst;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 其他数据函数// ...private:Singleton() { ... }static Singleton inst;// 其他数据成员// ...
};
Singleton Singleton::inst;
虽然它也是 先getInstance()再访问,但这种不是Meyers’ Singleton!
那么为什么Meyers推荐的是第一种的呢?
原因是这解决了一类重要问题,那就是static变量的初始化顺序的问题。
C++只能保证在同一个文件中声明的static变量的初始化顺序与其变量声明的顺序一致。但是不能保证不同的文件中的static变量的初始化顺序。
然后对于单例模式而言,不同的单例对象之间进行调用也是常见的场景。比如我有一个单例,存储了程序启动时加载的配置文件的内容。另外有一个单例,掌管着一个全局唯一的日志管理器。在日志管理初始化的时候,要通过配置文件的单例对象来获取到某个配置项,实现日志打印。
这时候两个单例在不同文件中各自实现,很有可能在日志管理器的单例使用配置文件单例的时候,配置文件的单例对象是没有被初始化的。这个未初始化可能产生的风险指的是C++变量的未初始化,而不是说配置文件未加载的之类业务逻辑上的未初始化导致的问题。
而Meyers’ Singleton写法中,单例对象是第一次访问的时候(也就是第一次调用getInstance()函数的时候)才初始化的,但也是恰恰因为如此,因而能保证如果没有初始化,在该函数调用的时候,是能完成初始化的。所以先getInstance()再访问 这种形式的单例 其关键并不是在于这个形式。而是在于其内容,局部static变量能保证通过函数来获取static变量的时候,该函数返回的对象是肯定完成了初始化的!
编译器会负责管理局部静态变量,当程序结束时进行析构,看样子一切安好。然而,当生成的多个Singleton对象具有依赖关系时,Meyers singleton就无能为力了(隐式析构Singleton亦如此)。
假设我们有四个Singleton,Director(导演)、Scene(场景)、Layer(图层)、Log(日志)。我们处于一个简单的游戏中,该游戏只有一个导演,一个场景,一个图层,图层上会有若干精灵。我们依次创建,到Layer时初始化失败了,此时创建一个Log来记录崩溃原因。之后程序关闭,执行期的相关机制会来摧毁静态对象,摧毁的顺序是LIFO,所以会先摧毁Log,然后是Layer。但若是Scene关闭失败,此时再想向Log记录崩溃原因,由于Log已被摧毁,所以返回的用只是一个“空壳”,之后的操作便会发生不确定性行为,这种问题称为dead-reference。
dead-reference问题的原因在于C++并不保证静态对象析构的顺序,因此具有依赖关系的多个Singleton无法正确删除。
使用局部静态变量的另一个缺点是难以通过派生子类来扩展Singleton,因为instance创建的始终都是singleton类型的对象。不过可以通过泛型编程来解决,见5节。
4 可以复活的Singleton
要满足具有依赖关系的Singleton,其中一个思路是当需要再次用到已经被执行期处理机制删除的Singleton对象时,使其死灰复燃,复活的Singleton需要自己负责删除。
所以首先需要增加一个标志来记录singleton是否已被摧毁,若已摧毁,则使其复生;若未摧毁,则创建Singleton对象。
大体上的代码如下:
1class singleton2{3public:4 static singleton& instance()5 {6 if(!pInstance_)7 {8 if(destroyed_)9 on_dead_reference(); // 出现dead-reference,重生singleton
10 else
11 create(); // 创建singleton
12 }
13 return *pInstance_;
14 }
15private:
16 static singleton *pInstance_;
17 static bool destroyed_; // 记录是否已被摧毁
18};
那么具体就是要看on_dead_reference和create这两个函数是如何处理的,create和之前一样,可以直接使用Meyers singleton:
1static void create()
2{
3 static singleton obj;
4 pInstance = &obj;
5}
当singleton对象析构时,也就是摧毁实例时,改变destroyed_标记(默认为false),将其改为摧毁状态:
1virtual ~singleton()
2{
3 pInstance = nullptr;
4 destroyed_ = true;
5}
而如何让对象死灰复燃呢?我们可以借助placement new,在对象死去的“空壳”上重新创建对象。
1void on_dead_reference()
2{
3 create();
4 new(pInstance_) singleton;
5 std::atexit(kill_singleton);
6 destroyed_ = false;
7}
这里的关键在于atexit函数,该函数可以注册一些函数,注册的函数将会在程序结束之时被调用。事实上static成员变量的默认释放也是通过此函数完成的,编译器会自动生成释放函数向atexit注册,释放机制是LIFO。
此时我们是通过placement new重新构造了singleton对象,所以需要自己释放,因此定义一个kill_singleton函数:
1static void kill_singleton()
2{
3 pInstance_->~singleton();
4}
只需调用析构函数释放便可。
reference: https://mp.weixin.qq.com/s/ufDK34vWC6yTSkS3TmucWw
https://www.zhihu.com/question/56527586
https://github.com/lkimuk/okdp/blob/master/singleton/singleton.hpp
CPP design pattern Singleton相关推荐
- Design Pattern - Singleton(C#)
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Defi ...
- Design pattern : Singleton
Singleton 语义: 在整个应用程序里只能创建唯一一个对象,并且提供全局的访问点: 实现: 1.先看一个经常性的一个错误实现: class Singleton { private: Si ...
- Design Pattern: Singleton 模式
一句话概括:保证一个类仅有一个实例,并提供一个访问它的全局访问点. Singleton的英文意义是独身,也就是只有一个人,应用在物件导向语言上,通常翻译作单例:单一个实例(Instance). 很多 ...
- python singleton design pattern super() 多继承
python singleton design pattern decorate baseclass metaclass import module super() 一.A decorator de ...
- Java Singleton Design Pattern (单例模式 SE7 Gold)
singleton design pattern offer 2 things: one and only one instance of the class, and a global single ...
- 源于Design Pattern Explanation with C++ Implementation的 设计模式 C++
1.创建型模式 (Creational Pattern) 1.1 工厂模式(Factory Pattern ) 1.2 抽象工厂模式(Abstract Factory Pattern ) 1.3 ...
- (原創) 我的Design Pattern之旅[3]:使用template改進Strategy Pattern (OO) (Design Pattern) (C/C++) (template)...
在strategy pattern中,為了讓各strategy能方便存取原來物件的所有public member function,我們常用*this將整個物件傳給各strategy,這樣的設計並沒有 ...
- 说说设计模式~大话目录(Design Pattern)
回到占占推荐博客索引 设计模式(Design pattern)与其它知识不同,它没有华丽的外表,没有吸引人的工具去实现,它是一种心法,一种内功,如果你希望在软件开发领域有一种新的突破,一个质的飞越,那 ...
- Design Pattern Quick Overview
Do we really need this design pattern? Just ignore, all the big guys requires it. For you, just unde ...
- 初读设计模式-----《design pattern explained》读后感
从网上淘来了一本<design pattern explained>,用了半个月的时间细细的读完了这本书. 本打算单单的从技术角度进行总结的,但是却全然没有头绪.说说自己的粗浅的感悟吧.. ...
最新文章
- php判断网页编码,python判断网页编码的方法
- [C#] Delegate, Multicase delegate, Event
- 2016.03.04,英语,《Vocabulary Builder》Unit 04
- linux 2G内存够用吗,不管2G还是6G运存,为什么使用手机时都会先占用一半?
- 2018-2019-2 网络对抗技术 20165320 Exp2 后门原理与实践
- 网页中弹出自定义对话框,并进行传值
- object string java_java实现Object转String的4种方法小结
- 建议阅读的投资经典55本
- 注册表右键取得管理员权限
- 关闭计算机后为什么重新启动,电脑关机后自动重启是怎么回事什么原因的解决方法 - 系统家园...
- 编写一个推导式,生成包含100以内所有勾股数(i,j,k)的列表
- Linux基础知识学习
- 小游戏2048最佳算法怎么实现?思路全解析!
- intel服务器无线网卡,刷版本号?Intel再发新版无线网卡驱动
- Unity 雨水滴到屏幕效果
- 品牌对比 | 佰草集 VS 膜法世家
- 夺冠!小米在 IWSLT 2022国际机器同传比赛中获佳绩
- 防沉迷与身份证系统挂钩 网游要实名认证
- 丁林松老师的QT视频及教程源码
- adb shell 查看和修改电池信息
热门文章
- python 马赛克还原_「马赛克画」利用Python生成马赛克画,简单两步去除马赛克! - seo实验室...
- 10万字公安大数据平台建设项目方案
- kubernetes日志架构
- 将《先生请出山》放到桌面上,聊聊如何将视频做成动态壁纸
- Py第四问 from test import test1 ImportError:cannot import name 'test1'
- 十、线程(11 章)
- 在线检测本机ip的网站
- JavaScript学习笔记01——简介(李炎恢JavaScript教程)
- 2019计算机就业形势图表分析,2019毕业生就业形势分析
- yaml UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0xba in position 15: illegal multibyte sequen