探测器 C++ Singleton(辛格尔顿)

  一、静态模式不是单一的情况下,
  刚开始学习的人可能误, 误以为所有的成员变量和成员方法用于 static , 就是单例模式了:
class Singleton
{
public:
    /* static method */
    
private:
    static Singleton m_data; //static data member 在类中声明。在类外定义
};

Singleton Singleton::m_data;
    
    乍一看确实具备单例模式的非常多条件, 只是它也有一些问题. 第一, 静态成员变量初始化顺序不依赖构造函数, 得看编译器心情的, 没法保证初始化顺序 (极端情况: 有 a b 两个成员对象, b 须要把 a 作为初始化參数传入, 你的类就 必须 得要有构造函数, 并确保初始化顺序).
  第二, 最严重的问题, 失去了面对对象的重要特性 -- "多态", 静态成员方法不可能是 virtual 的(补充一点,静态成员方法也不可能是 const 的. Singleton类的子类没法享受 "多态" 带来的便利.

  二、饿汉模式
  饿汉模式 是指单例实例在程序执行时被马上执行初始化:

class Singleton
{
public:
    static Singleton& getInstance()
    {
        return m_data;
    }
    
private:
    static Singleton m_data; //static data member 在类中声明。在类外定义
    Singleton(){}
    ~Singleton(){}
};

Singleton Singleton::m_data;
  
    这样的模式的问题也非常明显, 类如今是多态的, 但静态成员变量初始化顺序还是没保证: 假如有两个单例模式的类 ASingleton 和 BSingleton, 某天你想在 BSingleton 的构造函数中使用 ASingleton 实例, 这就出问题了. 由于 BSingleton m_data 静态对象可能先 ASingleton 一步调用初始化构造函数, 结果 ASingleton::getInstance() 返回的就是一个未初始化的内存区域, 程序还没跑就直接崩掉。

恩。这仅仅是理论分析的结果。以下给出一个简单的样例说明一下问题所在吧!


    实例:ASingleton、BSingleton两个单例类。当中 ASingleton 的构造函数中使用到 BSingleton 的单例对象。

class ASingleton
{
public:
    static ASingleton* getInstance()
    {
        return &m_data;
    }
    void do_something()
    {
        cout<<"ASingleton do_something!"<<endl;
    }
protected:
    static ASingleton m_data; //static data member 在类中声明。在类外定义
    ASingleton();
    ~ASingleton() {}
};

class BSingleton
{
public:
    static BSingleton* getInstance()
    {
        return &m_data;
    }
    void do_something()
    {
        cout<<"BSingleton do_something!"<<endl;
    }
protected:
    static BSingleton m_data; //static data member 在类中声明,在类外定义
    BSingleton();
    ~BSingleton() {}
};

ASingleton ASingleton::m_data;
BSingleton BSingleton::m_data;

ASingleton::ASingleton()
{
    cout<<"ASingleton constructor!"<<endl;
    BSingleton::getInstance()->do_something();
}

BSingleton::BSingleton()
{
    cout<<"BSingleton constructor!"<<endl;
}
    
    在这个測试样例中,我们将上述代码放在一个 main.cpp 文件里,当中 main 函数为空。
int main()
{
    return 0;
}
   
     执行測试结果是:
ASingleton constructor!
BSingleton do_something!
BSingleton constructor!

奇怪了,为什么 BSingleton 的构造函数竟然是在成员函数 do_something 之后调用的?

以下进行分析:
    首先我们看到这个測试用例中。因为仅仅有一个源文件,那么按从上到下的顺序进行编译执行。注意到:
ASingleton ASingleton::m_data;
BSingleton BSingleton::m_data;
    
    这两个定义式,那么就会依次调用 ASingleton 的构造函数 和 BSingleton 的构造函数进行初始化。

    一步一步来,首先是 ASingleto 的 m_data。
    那么程序就会进入 ASingleton 的构造函数中运行,即:
ASingleton::ASingleton()
{
    cout<<"ASingleton constructor!"<<endl;
    BSingleton::getInstance()->do_something();
}
    
    首先运行 cout,然后接着要获取 BSingleton 的单例,尽管说 BSingleton 的定义尚未运行。即  BSingleton BSingleton::m_data; 语句尚未运行到。可是 BSingleton 类中存在着其声明,那么还是能够调用到其 do_something 方法的。

    ASingleton 的构造函数运行完成。那么 ASingleton ASingleton::m_data;  语句也就运行结束了,即 ASingleton 单例对象 m_data 也就初始化完毕了。


    接下来运行 BSingleton BSingleton::m_data; 语句,那么也就是运行 BSingleton 的构造函数了。
    所以就有了终于结果的输出了。


那么到此,我们也许会说:既然 ASingleton 的构造函数中要用到 BSingleton 单例对象,那么就先初始化 BSingleton 的单例对象咯,是的,我们能够调换一下顺序:

//ASingleton ASingleton::m_data;
//BSingleton BSingleton::m_data;
//改动成:
BSingleton BSingleton::m_data;
ASingleton ASingleton::m_data;

  再执行一下,会发现输出的结果就正常了。

ASingleton constructor!
BSingleton constructor!
BSingleton do_something!
  
    问题攻克了。那么我们通过这个问题实例,我们对于 静态成员变量 初始化顺序没有保障 有了更深刻的理解了。

    在这个简单的样例中。我们通过调换代码位置能够保障 静态成员变量 的初始化顺序。可是在实际的编码中是不可能的。class 文件声明在头文件(.h)中,class 的定义在源文件(.cpp)中。而类静态成员变量声明是在 .h 文件里。定义在 .cpp 文件里。那么其初始化顺序就全然依靠编译器的心情了。所以这也就是 类静态成员变量 实现单例模式的致命缺点。
    当然。假如不出现这样的:在某单例的构造函数中使用到还有一个单例对象  的使用情况。那么还是能够接受使用的。

    三、懒汉模式:单例实例仅仅在第一次被使用时进行初始化:
class Singleton
{
public:
    static Singleton* getInstance()
    {
        if(! m_data) m_data = new Singleton();
        return m_data;
    }
    
private:
    static Singleton* m_data; //static data member 在类中声明,在类外定义
    Singleton(){}
    ~Singleton(){}
};

Singleton* Singleton::m_data = nullptr; 
 getInstance() 仅仅在第一次被调用时为 m_data 分配内存并初始化. 嗯, 看上去全部的问题都攻克了, 初始化顺序有保证, 多态也没问题.
    可是仅仅是看似没有问题而已,事实上当中存在着两个问题:

①线程不安全:我们注意到在 static Singleton* getInstance() 方法中,是通过 if 语句推断 静态实例变量 是否被初始化来认为是否进行初始化,那么在多线程中就有可能出现多次初始化的问题。

例如说,有两个多线程同一时候进入到这种方法中,同一时候运行 if 语句的推断,那么就会出现两次两次初始化静态实例变量的情况。


②析构函数没有被运行: 程序退出时, 析构函数没被运行. 这在某些设计不可靠的系统上会导致资源泄漏, 比方文件句柄, socket 连接, 内存等等. 幸好 Linux / Windows 2000/XP 等经常使用系统都能在程序退出时自己主动释放占用的系统资源. 只是这仍然可能是个隐患。
  对于这个问题, 比較土的解决方法是, 给每一个 Singleton 类加入一个 destructor() 方法:

virtual bool destructor()
{
    // ... release resource
    if (nullptr != m_data)
    {
        delete m_data;
        m_data = nullptr;
    }  
}
  
    然后在程序退出时确保调用了每一个 Singleton 类的 destructor() 方法, 这么做尽管可靠, 但却非常是繁琐.
  
    四、懒汉模式改进版:使用局部静态变量

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton theSingleton;
        return theSingleton;
    }
    /* more (non-static) functions here */
 
private:
    Singleton();                            // ctor hidden
    Singleton(Singleton const&);            // copy ctor hidden
    Singleton& operator=(Singleton const&); // assign op. hidden
    ~Singleton();                           // dtor hidden
};

可是这样的方式也存在着非常多的问题:
①随意两个单例类的构造函数不能相互引用对方的实例,否则会导致程序崩溃。如:
ASingleton& ASingleton::getInstance() {
    const BSingleton& b = BSingleton::getInstance();
    static ASingleton theSingleton;
    return theSingleton;
}
 
BSingleton& BSingleton::getInstance() {
    const ASingleton & b = ASingleton::getInstance();
    static BSingleton theSingleton;
    return theSingleton;
}

②多个 Singleton 实例相互引用的情况下, 须要慎重处理析构函数. 如: 初始化顺序为 ASingleton »BSingleton » CSingleton 的三个 Singleton 类, 当中 ASingleton BSingleton 的析构函数调用了CSingleton 实例的成员函数, 程序退出时, CSingleton 的析构函数 将首先被调用, 导致实例无效, 那么兴许 ASingleton BSingleton 的析构都将失败, 导致程序异常退出.

在局部作用域下的静态变量在编译时,编译器会创建一个附加变量标识静态变量是否被初始化,会被编译器变成像以下这样(伪代码):
static Singleton &Instance()
{
  static bool constructed = false;
  static uninitialized Singleton instance_;
  if (!constructed) {
    constructed = true;
    new(&s) Singleton; //construct it
  }
  return instance_;
}

    那么,在多线程的应用场合下必须小心使用. 假设唯一实例尚未创建时, 有两个线程同一时候调用创建方法, 且它们均没有检測到唯一实例的存在, 便会同一时候各自创建一个实例, 这样就有两个实例被构造出来, 从而违反了单例模式中实例唯一的原则. 解决问题的办法是为指示类是否已经实例化的变量提供一个相互排斥锁 (尽管这样会减少效率).

加锁例如以下:
static Singleton &getInstance()
{
    Lock();
    //锁自己实现 static
    Singleton instance_;
    UnLock();
 
    return instance_;
}
    但这样每次调用instance()都要加锁解锁。代价略大。

    五、终极方案
    在前面的讨论中。单例类中的静态对象不管是作为静态局部对象还是作为类静态全局变量都有问题。那么有什么更好的解决方式呢?
    boost 的实现方式是:单例对象作为静态局部变量。然后添加一个辅助类,并声明一个该辅助类的类静态成员变量,在该辅助类的构造函数中。初始化单例对象。
实现例如以下:
class Singleton
{
public:
    static Singleton* getInstance()
    {
        static Singleton instance;
        return &instance;
    }
    
protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            Singleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    Singleton() {}
    ~Singleton() {}
};
Singleton::Object_Creator Singleton::_object_creator;

    在前面的方案中:饿汉模式中,使用到了类静态成员变量,可是遇到了初始化顺序的问题; 懒汉模式中。使用到了静态局部变量,可是存在着线程安全等问题。

    那么在这个终极方案中能够说综合了以上两种方案。即採用到了类静态成员变量,也採用到了静态局部变量。

    注意到当中的辅助结构体 Object_Creator (能够称之为 proxy-class)所声明的类静态成员变量,初始化该静态成员变量时,当中的构造函数 调用了单例类的 getInstance 方法。

这样就会调用到 Singleton::getInstance() 方法初始化单例对象。那么自然 Singleton 的构造函数也就运行了。


    我们能够在Singleton 和 Object_Creator 的构造函数中加入一些输出信息:
class Singleton
{
public:
    static Singleton* getInstance()
    {
        static Singleton instance;
        return &instance;
    }

protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            cout<<"Object_Creator constructor"<<endl;
            Singleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    Singleton() {cout<<"Singleton constructor"<<endl;}
    ~Singleton() {}
};
Singleton::Object_Creator Singleton::_object_creator;

执行我们会看到(在 main 函数中还未使用到该单例):
Object_Creator constructor
Singleton constructor
说明,此时在main函数之前就初始化了单例对象。

对于前面的ASingleton 和 BSingleton 的样例,改进例如以下:
class ASingleton
{
public:
    static ASingleton* getInstance()
    {
        static ASingleton instance;
        return &instance;
    }
    void do_something()
    {
        cout<<"ASingleton do_something!"<<endl;
    }
protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            ASingleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    ASingleton();
    ~ASingleton() {}
};


class BSingleton
{
public:
    static BSingleton* getInstance()
    {
        static BSingleton instance;
        return &instance;
    }
    void do_something()
    {
        cout<<"BSingleton do_something!"<<endl;
    }
protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            BSingleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    BSingleton();
    ~BSingleton() {}
};
ASingleton::Object_Creator ASingleton::_object_creator;
BSingleton::Object_Creator BSingleton::_object_creator;

ASingleton::ASingleton()
{
    cout<<"ASingleton constructor!"<<endl;
    BSingleton::getInstance()->do_something();
}
BSingleton::BSingleton()
{
    cout<<"BSingleton constructor!"<<endl;
}

    这样程序就避免了 ASingleton 和 BSingleton 单例对象的初始化顺序问题,使得输出结果就始终是:
ASingleton constructor!
BSingleton constructor!
BSingleton do_something!

最后,展示一下加入了模板的实现:
template <typename T>
class Singleton
{
    struct object_creator
    {
        object_creator()
        {
            Singleton<T>::instance();
        }
        inline void do_nothing() const {}
    };

    static object_creator create_object;

public:
    typedef T object_type;
    static T& instance()
    {
        static T obj;
        //这个do_nothing是确保create_object构造函数被调用
        //这跟模板的编译有关
        create_object.do_nothing();
        return obj;
    }

};
template <typename T> typename Singleton<T>::object_creator Singleton<T>::create_object;

class QMManager
{
protected:
    QMManager() {}
    ~QMManager() {}
    friend class Singleton<QMManager>;
public:
    void do_something() {};
};

int main()
{
    Singleton<QMManager>::instance().do_something();
    return 0;
}

    boost 通过加入一个类似 proxy-class 的方式,实现了单例模式,可是显然添加了复杂性,在实际应用中应该依据实际情况採用适当的实现方案。





posted @ 2015-06-10 18:48  zfyouxi  阅读(293)  评论(0编辑  收藏  举报