Singletons(单件)实作技术 - c++设计新思维

来源:互联网 发布:移动数据流量资费 编辑:程序博客网 时间:2024/05/13 02:50

保证一个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. 

原创粉丝点击