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相关推荐

  1. Design Pattern - Singleton(C#)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Defi ...

  2. Design pattern : Singleton

    Singleton 语义: 在整个应用程序里只能创建唯一一个对象,并且提供全局的访问点: 实现: 1.先看一个经常性的一个错误实现:     class Singleton { private: Si ...

  3. Design Pattern: Singleton 模式

    一句话概括:保证一个类仅有一个实例,并提供一个访问它的全局访问点. Singleton的英文意义是独身,也就是只有一个人,应用在物件导向语言上,通常翻译作单例:单一个实例(Instance).  很多 ...

  4. python singleton design pattern super() 多继承

    python  singleton design pattern decorate baseclass metaclass import module super() 一.A decorator de ...

  5. Java Singleton Design Pattern (单例模式 SE7 Gold)

    singleton design pattern offer 2 things: one and only one instance of the class, and a global single ...

  6. 源于Design Pattern Explanation with C++ Implementation的 设计模式 C++

    1.创建型模式 (Creational Pattern) 1.1 工厂模式(Factory Pattern ) 1.2  抽象工厂模式(Abstract Factory Pattern ) 1.3   ...

  7. (原創) 我的Design Pattern之旅[3]:使用template改進Strategy Pattern (OO) (Design Pattern) (C/C++) (template)...

    在strategy pattern中,為了讓各strategy能方便存取原來物件的所有public member function,我們常用*this將整個物件傳給各strategy,這樣的設計並沒有 ...

  8. 说说设计模式~大话目录(Design Pattern)

    回到占占推荐博客索引 设计模式(Design pattern)与其它知识不同,它没有华丽的外表,没有吸引人的工具去实现,它是一种心法,一种内功,如果你希望在软件开发领域有一种新的突破,一个质的飞越,那 ...

  9. Design Pattern Quick Overview

    Do we really need this design pattern? Just ignore, all the big guys requires it. For you, just unde ...

  10. 初读设计模式-----《design pattern explained》读后感

    从网上淘来了一本<design pattern explained>,用了半个月的时间细细的读完了这本书. 本打算单单的从技术角度进行总结的,但是却全然没有头绪.说说自己的粗浅的感悟吧.. ...

最新文章

  1. php判断网页编码,python判断网页编码的方法
  2. [C#] Delegate, Multicase delegate, Event
  3. 2016.03.04,英语,《Vocabulary Builder》Unit 04
  4. linux 2G内存够用吗,不管2G还是6G运存,为什么使用手机时都会先占用一半?
  5. 2018-2019-2 网络对抗技术 20165320 Exp2 后门原理与实践
  6. 网页中弹出自定义对话框,并进行传值
  7. object string java_java实现Object转String的4种方法小结
  8. 建议阅读的投资经典55本
  9. 注册表右键取得管理员权限
  10. 关闭计算机后为什么重新启动,电脑关机后自动重启是怎么回事什么原因的解决方法 - 系统家园...
  11. 编写一个推导式,生成包含100以内所有勾股数(i,j,k)的列表
  12. Linux基础知识学习
  13. 小游戏2048最佳算法怎么实现?思路全解析!
  14. intel服务器无线网卡,刷版本号?Intel再发新版无线网卡驱动
  15. Unity 雨水滴到屏幕效果
  16. 品牌对比 | 佰草集 VS 膜法世家
  17. 夺冠!小米在 IWSLT 2022国际机器同传比赛中获佳绩
  18. 防沉迷与身份证系统挂钩 网游要实名认证
  19. 丁林松老师的QT视频及教程源码
  20. adb shell 查看和修改电池信息

热门文章

  1. python 马赛克还原_「马赛克画」利用Python生成马赛克画,简单两步去除马赛克! - seo实验室...
  2. 10万字公安大数据平台建设项目方案
  3. kubernetes日志架构
  4. 将《先生请出山》放到桌面上,聊聊如何将视频做成动态壁纸
  5. Py第四问 from test import test1 ImportError:cannot import name 'test1'
  6. 十、线程(11 章)
  7. 在线检测本机ip的网站
  8. JavaScript学习笔记01——简介(李炎恢JavaScript教程)
  9. 2019计算机就业形势图表分析,2019毕业生就业形势分析
  10. yaml UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0xba in position 15: illegal multibyte sequen