需求描述(实现一个线程安全且无内存泄漏的C++单例模式):
1) 是一个"懒汉"单例模式,按需内存分配。
2) 基于模板实现,具有很强的通用性。
3) 自动内存析构,不存在内存泄露问题(使用std::tr1::shared_ptr)。
4) 在多线程情况下,是线程安全的。
5) 尽可能的高效。(线程安全必定涉及到线程同步,线程同步分为内核级别和用户级别的
同步对象,用户级别效率远高于内核级别的同步对象,而用户级别效率最高的是
InterlockedXXXX系列API)。
6) 这个实际上也是一个Double-Checked Locking实现的单例模式。是传统的Double-
Checked-Locking变异版本。
线程安全的单例模式实现(基础版)
线程安全的单例模式(基础版)的测试
线程安全的单例模式(基础版)存在的不足
单例的一个原则就是禁止构造函数和析构函数为public,防止外部实例化,仅允许调用GetInstance()等静态方法进行初始化。
由于使用模板技术,如果我们不将基类和子类的构造和析构函数设置为public级别,模板实例化导致编译器报错。
线程安全的单例模式(基础版)的修正
1、修正构造函数:
1) 将基类CSingletonPtr的构造函数为protected访问级别。
2) 每一个继承自CSingletonPtr的子类也将构造函数声明为protected访问级别,并在继
承类中声明友元类。
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,代表一个删除器。
重载函数调用操作符,该删除器类实际是一个仿函数对象。
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的引用。
更进一步,我们使用编译器提供的本质函数。
线程安全的单例模式(修正版)的优缺点
1、优点:该实现是一个"懒汉"单例模式,意味着只有在第一次调用GetInstance()
静态方法的时候才进行内存分配。
通过模板和继承方式,获得了足够通用的能力。
在创建单例实例的时候,具有线程安全性。
通过智能指针方式,防止内存泄露。
具有相对的高效性。
2、 缺点:肯定没有单线程版本的效率高。
每个子类必须要授权基类,我们可以写一个宏减少输入:
#define DECLARE_SINGLETON_CLASS(type) \
friend class CSingletonPtr <type>;
饿汉类型单例模式实现(终极版)
饿汉模式意味着在主线程(main函数代表主线程)之前就对类进行内存分配和初始化。实现代码如下:
饿汉类型单例模式测试
单例模式四种实现总结
从编译器以及是否线程安全方面考虑:
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++内存操作的主流技术。
**