Published by orzz.org(). (http://orzz.org/cpp%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b9%8bsingleton%e5%8d%95%e4%bb%b6%e5%8d%95%e4%be%8b%e6%a8%a1%e5%bc%8f/)
单例模式,顾名思义,此模式下的对象实例永远只有一个.很多初学者感觉这个模式的用处不大.但实际上,这个模式的应用非常广泛.
很多情况下,我们会很自然的使用单例的方式来实现功能,如全局内存池;全局资源管理器;某个全局的工具类工厂...在UI开发上,同一时间点上仅会出现一个实例的对话框,也可以使用单例实现.
单例的类图很简单:
它的目的,就是给程序提供一个全局唯一的访问点,用于访问某些资源;固定的算法或唯一的对象等.
单例模式的构建方式主要有两种:饿汉方式与懒汉方式.也就是静态初始化的单例与运行时根据需要初始化的单例.
- 饿汉方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// 静态单例类 class CSingletonStatic sealed { private: // 存储唯一的实例 static const TSmartPtr<CSingletonStatic> instance; private: CSingletonStatic() { } public: static CSingletonStatic* GetInstance() { _tcout << _T("获得一个实例:") << (CSingletonStatic*)instance << endl; return instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; // 指针初始化化 const TSmartPtr<CSingletonStatic> CSingletonStatic::instance = new CSingletonStatic; |
- 懒汉方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// 单例类 class CSingleton { private: // 存储唯一的实例 static TSmartPtr<CSingleton> instance; private: CSingleton() { } public: static CSingleton* GetInstance() { if( instance == NULL ) { _tcout << _T("创建一个实例:"); instance = new CSingleton; _tcout << (CSingleton*)instance << endl; } _tcout << _T("获得一个实例:") << (CSingleton*)instance << endl; return instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; // 指针实例化 TSmartPtr<CSingleton> CSingleton::instance; //////////////////////////////////////////////////////////////////// // 单例类(内部静态初始化) class CSingletonStaticInside sealed { private: CSingletonStaticInside() { } public: static CSingletonStaticInside* GetInstance() { static CSingletonStaticInside instance; _tcout << _T("获得一个实例:") << &instance << endl; return &instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; |
当我们在实际应用中,很可能需要考虑线程安全问题.饿汉方式由于实现采用类成员静态初始化,始终是在主线程的主函数开始之前,以单线程方式进行的.所以当程序开启多个线程开始同步访问此单例类的时候,它总能返回唯一的单例.
然而懒汉方式则都是非线程安全的.就算是使用函数内部静态初始化的懒汉单例,由于目前的C++标准并没有规定编译器需要解决static的线程安全性,因此它也不是线程安全的.
也就是说,一个static声明的类变量,在多线程同时第一次访问时将有可能被构造多次(普通类型的变量,如bool,int的static是线程安全的.在C++0x中规定了static必须由编译器解决线程安全问题,因此支持C++0x的编译器编译的static类变量应该也不会有线程安全问题).
因此,我们可能需要这样来写线程安全的懒汉单例(注意,我这里使用的锁仅是语义上的"锁",在实际应用中需要使用对应平台提供的锁来完成相应操作):
- 线程安全的懒汉方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
// 单例类 double-checked(双重检测) class CSingleton { private: // 存储唯一的实例 static TSmartPtr<CSingleton> instance; // 线程锁标记 static bool b_lock; private: CSingleton() { } public: // 对过程加锁 static void LockMutex(bool& b_lock = CSingleton::b_lock) { while( b_lock ); b_lock = true; } // 对过程解锁 static void UnlockMutex(bool& b_lock = CSingleton::b_lock) { b_lock = false; } public: static CSingleton* GetInstance() { if( instance == NULL ) { LockMutex(); { if( instance == NULL ) { _tcout << _T("创建一个实例:"); instance = new CSingleton; _tcout << (CSingleton*)instance << endl; } } UnlockMutex(); } _tcout << _T("获得一个实例:") << (CSingleton*)instance << endl; return instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; // 指针实例化 TSmartPtr<CSingleton> CSingleton::instance; bool CSingleton::b_lock = false; ////////////////////////////////////////////////////////////////// // 单例类(内部静态初始化) class CSingletonStaticInside sealed { private: CSingletonStaticInside() { } public: // 对过程加锁 static void LockMutex(bool& b_lock) { while( b_lock ); b_lock = true; } // 对过程解锁 static void UnlockMutex(bool& b_lock) { b_lock = false; } public: static CSingletonStaticInside* GetInstance() { // 线程锁标记 static bool b_lock; LockMutex(b_lock); static CSingletonStaticInside instance; UnlockMutex(b_lock); _tcout << _T("获得一个实例:") << &instance << endl; return &instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; |
在实际项目中,很多时候我们会用泛型的思想做单例模式,例如如下代码:
- 利用模板实现较为通用的单例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
template <typename TypeT> class CSingletonT { public: static TypeT m_Type; public: static TypeT& Instance() { return m_Type; } }; template <typename TypeT> TypeT CSingletonT<TypeT>::m_Type; |
这是一个利用模板实现的饿汉单例,并且是线程安全的.其他方式实现的单例模式也可以类似推出对应的模板实现.
上面说了那么多,可以看出来,最方便的实现方式,应该是使用类成员静态初始化实现的饿汉方式单例了.但是这种单例模式有个最大的硬伤,就是在C++里默认的静态成员初始化顺序是不确定的.当一个程序中有多个单例,并且单例之间有相互的依赖时,就很可能出现因为构造顺序的不一致,导致当访问某些单例时出现返回的对象尚未初始化的问题.
解决的方法有很多种,在这里我一般是确定一个会被其他单例类依赖的单例,将它采用懒汉模式实现,而其他的单例则使用饿汉模式.此时的懒汉单例也不需要加锁,因为它必定会被其他的饿汉在构造的时候调用一次.
下面给出完整的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
////////////////////////////////////////////////////////////////// // 单例模式 C++示例 // // Author: 木头云 // Blog: http://darkc.at // E-Mail: memleak@darkc.at // Version: 1.0.1022.112(2009/10/22) ////////////////////////////////////////////////////////////////// #include "stdafx.h" ////////////////////////////////////////////////////////////////// // 单例类 class CSingleton { private: // 存储唯一的实例 static TSmartPtr<CSingleton> instance; // 线程锁标记 static bool b_lock; private: CSingleton() { } public: // 对过程加锁 static void LockMutex(bool& b_lock = CSingleton::b_lock) { while( b_lock ); b_lock = true; } // 对过程解锁 static void UnlockMutex(bool& b_lock = CSingleton::b_lock) { b_lock = false; } public: static CSingleton* GetInstance() { if( instance == NULL ) { LockMutex(); { if( instance == NULL ) { _tcout << _T("创建一个实例:"); instance = new CSingleton; _tcout << (CSingleton*)instance << endl; } } UnlockMutex(); } _tcout << _T("获得一个实例:") << (CSingleton*)instance << endl; return instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; // 指针实例化 TSmartPtr<CSingleton> CSingleton::instance; bool CSingleton::b_lock = false; //////////////////////////////////////////////////////////////////// // 静态单例类 class CSingletonStatic sealed { private: // 存储唯一的实例 static const TSmartPtr<CSingletonStatic> instance; private: CSingletonStatic() { } public: static CSingletonStatic* GetInstance() { _tcout << _T("获得一个实例:") << (CSingletonStatic*)instance << endl; return instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; // 指针初始化化 const TSmartPtr<CSingletonStatic> CSingletonStatic::instance = new CSingletonStatic; //////////////////////////////////////////////////////////////////// // 单例类(内部静态初始化) class CSingletonStaticInside sealed { private: CSingletonStaticInside() { } public: // 对过程加锁 static void LockMutex(bool& b_lock) { while( b_lock ); b_lock = true; } // 对过程解锁 static void UnlockMutex(bool& b_lock) { b_lock = false; } public: static CSingletonStaticInside* GetInstance() { // 线程锁标记 static bool b_lock; LockMutex(b_lock); static CSingletonStaticInside instance; UnlockMutex(b_lock); _tcout << _T("获得一个实例:") << &instance << endl; return &instance; } void Func() { _tcout << _T("实例方法调用:") << this << endl; } }; ////////////////////////////////////////////////////////////////// // 主函数 int _tmain(int argc, _TCHAR* argv[]) { // 单例类 _tcout << _T("单例类:") << endl; CSingleton::GetInstance()->Func(); CSingleton::GetInstance()->Func(); _tcout << endl; // 静态单例类 _tcout << _T("静态单例类:") << endl; CSingletonStatic::GetInstance()->Func(); CSingletonStatic::GetInstance()->Func(); _tcout << endl; // 单例类(内部静态初始化) _tcout << _T("单例类(内部静态初始化):") << endl; CSingletonStaticInside::GetInstance()->Func(); CSingletonStaticInside::GetInstance()->Func(); _tcout << endl; return 0; } ////////////////////////////////////////////////////////////////// /* Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点. Singleton模式类似于实用类的静态方法,但是利用Singleton模式可以保存类的状态,并可以提供继承多态之类的动态特征. 非静态初始化Singleton模式在当在第一次调用实例时初始化新实例,并在以后仅仅返回此实例; 静态初始化的Singleton模式在当程序运行时就创建了一个新的实例,并在以后仅仅返回此实例. 前者不需要提前占用系统资源,但是在多线程环境中使用时必须利用双重锁定之类的方式处理线程安全问题; 后者在程序运行时就占用了必须的系统资源,不过在多线程环境中也可以准确的返回唯一的实例. */ ////////////////////////////////////////////////////////////////// |