闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)

需求描述(实现一个线程安全且无内存泄漏的C++单例模式):

 1) 是一个"懒汉"单例模式,按需内存分配。

 2) 基于模板实现,具有很强的通用性。

 3) 自动内存析构,不存在内存泄露问题(使用std::tr1::shared_ptr)。

 4) 在多线程情况下,是线程安全的。

 5) 尽可能的高效。(线程安全必定涉及到线程同步,线程同步分为内核级别和用户级别的
     同步对象,用户级别效率远高于内核级别的同步对象,而用户级别效率最高的是  
     InterlockedXXXX系列API)。

 6) 这个实际上也是一个Double-Checked Locking实现的单例模式。是传统的Double-
     Checked-Locking变异版本。

线程安全的单例模式实现(基础版)

Paste_Image.png
Paste_Image.png
Paste_Image.png

线程安全的单例模式(基础版)的测试

Paste_Image.png
Paste_Image.png

线程安全的单例模式(基础版)存在的不足

Paste_Image.png
Paste_Image.png

单例的一个原则就是禁止构造函数和析构函数为public,防止外部实例化,仅允许调用GetInstance()等静态方法进行初始化。

由于使用模板技术,如果我们不将基类和子类的构造和析构函数设置为public级别,模板实例化导致编译器报错。

线程安全的单例模式(基础版)的修正

1、修正构造函数:

 1)  将基类CSingletonPtr的构造函数为protected访问级别。
Paste_Image.png
2)  每一个继承自CSingletonPtr的子类也将构造函数声明为protected访问级别,并在继
      承类中声明友元类。
Paste_Image.png
3) 在上述代码设定以后,我们会发现对于构造函数,通过子类授权给基类的方式,我们
     能够很顺利的通过编译,代码正确的运行。

这样我们解决了防止第三方调用Manager的构造函数,Manager类的构造函数只允许在
GetInstance()静态方法中被调用。

2、修正析构函数:
我们会发现,对于析构函数,它依旧是public访问级别的,为什么不让析构函数也声明为
protected级别呢?

因为由于我们使用了std::tr1::shared_ptr(与boost::shared_ptr基本一致),Manager类的析构委托给了shared_ptr ,而Manager授权给的是其基类。所以如果我们将析构函数设置为protected级别,编译器会报错。

那么我们第一个反应就是我们继续在Manager中授权shared_ptr。但是没成功。可能是由于shared_ptr实现的机制导致不能成功。

难道我们真的没有办法将析构函数修正为受保护级别吗?

山穷水尽疑无路,柳暗花明又一村!

强大的shared_ptr删除器的出现解决了我们的问题!

1)  在基类CSingletonPtr中声明并实现一个访问级别为private的嵌套类Deleter,代表一个删除器。
重载函数调用操作符,该删除器类实际是一个仿函数对象。
Paste_Image.png
Paste_Image.png
2) 在GetInstance()静态函数中增加删除器设置代码,见图红色部分。
    一旦我们设置好删除器,那么在shared_ptr析构时不会直接调用delete而是调用删除器。
   这样我们 就将子类T的析构函数隐藏起来,不被外部调用。

3)  每一个继承自CSingletonPtr的子类也将构造函数声明为protected访问级别,但不需
      要声明授权友元类。

通过上述步骤,我们将基类和子类的构造函数都声明为受保护级别,以防止外部调用。这样整个单例子类的生命周期都由shared_ptr控制。

3、修正GetInstance()静态方法:
基础版的GetInstance()静态方法返回的是tr1::shared_ptr<T>结构,这样导致每次调用
GetInstance()都会使shared_ptr的引用计数加1并调用shared_ptr的拷贝构造函数。在
调用完成GetInstance()->Print方法后,又将临时产生的shared_ptr对象引用计数减1,
这样对效率有非常大的影响。
我们要避免这种情况,那么我们要做的是修改代码,直接在GetInstance()中返回T的引用。

Paste_Image.png

更进一步,我们使用编译器提供的本质函数。

Paste_Image.png
Paste_Image.png

线程安全的单例模式(修正版)的优缺点

1、优点:该实现是一个"懒汉"单例模式,意味着只有在第一次调用GetInstance()
            静态方法的时候才进行内存分配。

             通过模板和继承方式,获得了足够通用的能力。

             在创建单例实例的时候,具有线程安全性。

             通过智能指针方式,防止内存泄露。

             具有相对的高效性。

2、 缺点:肯定没有单线程版本的效率高。

            每个子类必须要授权基类,我们可以写一个宏减少输入:

            #define DECLARE_SINGLETON_CLASS(type)   \
                          friend class CSingletonPtr  <type>;

饿汉类型单例模式实现(终极版)

饿汉模式意味着在主线程(main函数代表主线程)之前就对类进行内存分配和初始化。实现代码如下:

Paste_Image.png
Paste_Image.png

饿汉类型单例模式测试

Paste_Image.png
Paste_Image.png
Paste_Image.png

单例模式四种实现总结

从编译器以及是否线程安全方面考虑:

1、如果你使用vc6编译器,请放弃设计模式。

2、如果你整个程序是单线程的,那么标准模式或Meyers单例模式是你最佳选择。

3、如果你使用符合C++0X标准的编译器的话,由于C++0X标准规定:要求编译器保证内
   部静态变量的线程安全性。(vc2010及以上版本。因此Meyers单例模式是你最佳选择)。

4、如果你使用VC6以后,vc2010以下版本的编译器的话,并且需要线程安全,则使用实现的Double-Checked-Locking版本的单件模式。

从单例模式实现的角度考虑:

1、总是避免第三方调用拷贝构造函数以及赋值操作符

2、总是避免第三方调用构造函数

3、尽量避免第三方调用析构函数

4、总是需要一个静态方法用于全局访问

**本篇文档写于2010年,当时还是以vs2008为主,因此并没有符合c++0x标准。现如今都vs2015了,c++11标准都普及了,因此Meyers单例模式是你最佳选择。
不过关于上面的shared_ptr方面的应用,还是很有价值的。shared_ptr已成为目前c++内存操作的主流技术。
**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容