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

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

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

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

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

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

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

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

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

闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第1张图片
Paste_Image.png
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第2张图片
Paste_Image.png

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

闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第3张图片
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第4张图片
Paste_Image.png

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

闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第5张图片
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第6张图片
Paste_Image.png

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

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

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

1、修正构造函数:

 1)  将基类CSingletonPtr的构造函数为protected访问级别。
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第7张图片
Paste_Image.png
2)  每一个继承自CSingletonPtr的子类也将构造函数声明为protected访问级别,并在继
      承类中声明友元类。
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第8张图片
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,代表一个删除器。
重载函数调用操作符,该删除器类实际是一个仿函数对象。
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第9张图片
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第10张图片
Paste_Image.png
2) 在GetInstance()静态函数中增加删除器设置代码,见图红色部分。
    一旦我们设置好删除器,那么在shared_ptr析构时不会直接调用delete而是调用删除器。
   这样我们 就将子类T的析构函数隐藏起来,不被外部调用。

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

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

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

闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第11张图片
Paste_Image.png

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

Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第12张图片
Paste_Image.png

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

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

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

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

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

             具有相对的高效性。

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

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

            #define DECLARE_SINGLETON_CLASS(type)   \
                          friend class CSingletonPtr  ;

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

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

闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第13张图片
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第14张图片
Paste_Image.png

饿汉类型单例模式测试

闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第15张图片
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第16张图片
Paste_Image.png
闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下)_第17张图片
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++内存操作的主流技术。
**

你可能感兴趣的:(闲聊c/c++ 9: 设计模式: 单例模式真的简单吗?(下))