首页 > 代码库 > 单例模式
单例模式
1. 由单例模式中关于static的问题
2. 为什么要有单例模式
3. 多线程安全的单例模式
4. 模版类的单例模式的实现
1. 由单例模式中关于static的问题:“静态成员函数调用了非静态成员函数(构造函数)”
最简单的单例模式如下:
1 class Singleton { 2 private: 3 Singleton() {}; 4 ~Singleton() {}; 5 public: 6 static Singleton* getInstance() 7 { 8 if (_instance == nullptr) 9 { 10 _instance = new Singleton; 11 } 12 return _instance; 13 } 14 static void deleteInstance() 15 { 16 if (_instance != nullptr) 17 { 18 delete _instance; 19 _instance = nullptr; 20 } 21 } 22 private: 23 static Singleton* _instance; 24 }; 25 Singleton* Singleton::_instance = nullptr;
问题来了:在第10行,new 了一个Singleton,此时调用了Singleton的构造函数,看上去是不是“静态成员函数调用了非静态成员函数(构造函数)”,但实际上我们的常识是“静态成员函数只能调用静态成员函数”。这不是矛盾了么?
首先分析下这种常识背后的原因:普通成员函数(非静态)在编译器编译的时候会在函数参数上多加上一个this指针的参数,而静态成员函数则没有。因此如果在静态成员函数中调用了非静态成员函数,this指针无从而来。
再看那个问题,在new Singleton的时候第一步 分配了一段内存,也就有了这个实例的this指针,第二步再调用构造函数,此时需要的this指针就是第一步创建的。也就是说对这个新的对象 调用他的静态和非静态成员函数(当然是要有访问权限的函数)都是可以的,因为在调用非静态成员函数时的this指针是这个新的对象实例的地址,而不是需要通过函数参数传进来this指针。只不过问题中正好是生成的是一个本类的实例。
同时调用静态函数 Singleton::getInstance() 时,表明了作用域在 Singleton:: 内,那么就可以在这个函数里访问Singleton类里任何访问权限的成员。
还有需要注意的一点是:静态数据成员需要在类外进行定义,因为如果没有定义就是没有分配内存(类内的仅仅是声明而已),没有分配内存怎么可以访问呢。
2. 为什么要有单例模式
单例模式实现了 某类只能有一个实例,并且提供一个全局访问点。上述的代码中可以看到,这个唯一的实例,就作为此类的静态数据成员存在。getInstance就是全局访问点接口。(完整来说 除了私有化构造函数,还需要私有拷贝构造函数(其实就是构造函数一种)、赋值操作符。)
那么要实现只有一个实例,也可以用全局变量,大家都通过这个全局变量来访问操作。但是全局变量的缺点是,不管代码中实际用到与否,都会初始化。但是单例模式中的唯一实例只有真正用到的时候才会被创建,即调用getInstance时。
3. 多线程安全的单例模式
在生成实例的时候加锁:
static Singleton* getInstance() { if (_instance == nullptr) { 加锁操作 if (_instance == nullptr) // 想想这里为什么还要再判断一次 _instance = new Singleton; } return _instance; }
也可以在初始化的时候直接new, getInstance直接返回对象。不过这样就失去了他的一个优点—只有真正用到的时候才会被创建。
Class Singleton { .... Singleton* getInstance() { return _instance; } .... } Singleton* Singleton::_instance = new Singleton;
4. 模版类的单例模式的实现
模板类的单例模式如下:
template<typename T> class Singleton { protected: // protected确保子类可以创建对象 Singleton() {}; virtual ~Singleton() {}; // 如果析构函数里面有资源的释放什么的 就必须写上 virtual public: static T* getInstance() { if (_instance == nullptr) { _instance = new T; } return _instance; } static void deleteInstance() { if (_instance != nullptr) { delete _instance; _instance = nullptr; } } private: static T* _instance; }; template<typename T> T* Singleton<T>::_instance = nullptr;
之所以抽象一个模板类的单例模式就是为了复用代码,只需要把 想要单例化的类 继承自 以自己作为模板参数的Singleton就可以,比如要单例化SingletonA:
// 实例化模板参数, CRTP递归模板 class SingletonA :public Singleton<SingletonA> { //...一些其他的接口 private: //(项目中代码 构造函数变成了public 失去了单例的意义 尽管使用的时候都按照约定的 getInstance函数。) SingletonA() {}; ~SingletonA() {}; friend Singleton<SingletonA>; // 这样基类中才能访问到派生类的私有的构造函数 };
这里有几个问题:
1. CRTP递归模板,用一句话定义: 使用派生类 作为 模板参数 特化 基类。
2.
如果基类中的数据成员为:
static T _instance;
CRTP这种模式可以么?可以,因为静态数据成员不占类的内存空间。
3.
如果基类中的数据成员为:
T* _instance;
CRTP这种模式可以么?可以,因为指针占固定的大小。
4.
如果基类中的数据成员为:
T _instance;
CRTP这种模式可以么?不可以,因为T还没有定义出来。
参考:
《headfirst设计模式》
项目代码
单例模式