保证一个class 只有一个实例 . 并为它提供一个全局的访问点.
与一般全局变量相比. Singleto的特性.
用以支持Singleton的c++基本手法.
如何更好的厉行Singleton的唯一性.
摧毁Singleton并检验摧毁之后的访问动作.
实现Singleton对象的生命期高阶管理方案.
多线程相关问题.
用以支持Singleton的一些c++基本手法
class Singleton {
public:
static Singleton& Instance();
private:
//将构造.析构.赋值函数都声明为私有.
Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton();
};
访问单件对象的函数Instance()可能定义如下:
Singleton& Singleton::Instance() {
static Singleton obj; //局部静态对象.首次进入时产生.程序结束摧毁.
return obj;
}
这个定义简单.优雅.由ScottMeyers提出.
Dead(失效的) Reference 问题
当程序中有多个互相联系的Singletons时. 它们的初始化/摧毁 时间的控制很重要. 用
ScottMeyers的不能解决. 比如.当其中一个使用另一个.而后者已经摧毁.就会发生错误.
为了跟踪Singleton对象是否被摧毁.我们给Singleton类添加一个成员变量
static bool destroyed_; //构造时将它设为false.析构时将它设为true.
实作如下:
class Singleton{
public:
static Singleton& Instance() {
if (!pInstance_) {
//检查是否已经摧毁?
if (destroyed_)
OnDeadReference(); //如果已摧毁.执行此函数(例如抛出异常).
else
Create(); //产生一个Singleton对象.
//专门用一个Create函数.(遵守单一职责原则)
}
return *pInstance_;
}
private:
static void Create() { //产生Singleton对象.
static Singleton theInstance;
pInstance = &theInstance;
}
static void OnDeadReference() { //引用已摧毁的单件时调用此函数
throw std::runtime_error("Dead Reference Detected");
}
virtual ~Singleton() {
pInstance_ = 0;
destroyed_ = true;
}
//禁止构造.赋值函数....
static Singleton *pInstance_;
static bool destroyed_;
};
//在实现文件中
Singleton* Singleton::pInstance_ = 0;
bool Singleton::destroyed_ = false;
上边的实现里. 解决的引用已经摧毁的单件对象的问题. 即抛出异常.
但也可以用其他方法. 如让摧毁了的单件重生.(Phoenix Singleton).
让单件对象在引用被摧毁的对象时重生.在一些情况下可以正常工作. 但
重生的对象可能丢失了摧毁前的状态. 再说这种解决方法有些不直观.
再一种解决摧毁次序的方法是 : "带寿命的 Singletons "
它的思路是. 定义一个函数SetLongevity()来登记要delete的指针以及寿命.
所以它只能登记 new 出来的对象.
例如:
SomeClass *pObj1 = new SomeClass;
SetLongevity( *pObj1, 5); //这样就不用使用者delete这个指针了.
生活在多线程世界
如下代码在多线程中可能出错:
Singleton& Singleton::Instance() {
if(!pInstance_) // 1
pInstance_ = new Singleton; // 2
return *pInstance_ ;
}
当一个线程刚开始执行 1 时. pInstance_ 为 NULL .准备执行 2. 此时另一个线程也调用
Singleton::Instance(). 它也执行 1 .为 NULL . 也执行 2. 这就产生了两个Singleton对象.
这是典型的竞争条件问题. 解决方法是使用"互斥体"加锁.如:
Singleton& Singleton::Instance() {
//加锁.Lock的构造函数为互斥体(mutex)加锁.析构函数为其解锁.
//当mutex_被锁定时. 其他试图锁定同一个 mutex_ 的线程都必须等待.
Lock guard(mutex_); //
if (!pInstance_)
pInstance = new Singleton;
return *pInstance_;
}
这种方案解决了问题. 但每次调用Instance()都加锁一次. 缺乏效率.
一个尝试的修改如下:
Singleton& Singleton::Instance() {
if (!pInstance_) // 1
Lock guard(mutex_); // 在这儿加锁
pInstance = new Singleton;
return *pInstance_;
}
但这样修改是错误的. 如果第一个线程在将要 Lock 之前切换到了另一线程. 另一线程通样执行1...
解决这个问题的办法是 : "双检测锁定"
即在锁定前和锁定后都进行检测. 这样既保持了效率. 又保证了多线程中的正确性.代码如下:
Singleton& Singleton::Instance() {
if (!pInstance_) // 检测一次
{
Lock guard(mutex_); // 加锁
if (!pInstance_) // 再检测一次
pInstance = new Singleton;
}
return *pInstance_;
}
注意: 为了阻止编译器的优化. 应该将 pInstance_声明为 volatile .
最后. 是一个封装了的SingletonHolder类模板. 它用一个类型和各种策略作为模板参数.
例如创建的策略. 寿命策略. 线程策略.
用这些策略来定制合适的Singleton.