C++设计模式之单例模式

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

一、懒汉式

懒汉式的特点是第一次使用时构造,以时间换空间。

  • 静态成员实例的懒汉(线程不安全,多线程情况下可能New多个实例)
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (m_Instance == NULL)
        {
            m_Instance = new Singleton();
        }
        return m_Instance;
    }

private:
    Singleton();
    static Singleton *m_Instance;
};
Singleton *Singleton::m_Instance = NULL;
View Code
  • 改进的静态成员实例的懒汉(双检锁,线程安全):
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (m_Instance == NULL)
        {
            //C++标准库并没有提供lock()/unlock(),此处仅为了说明;若仅进行双检不加锁,也有线程安全问题,只是概率较小而已。
            lock();
            if (m_Instance == NULL)
            {
                instance = new Singleton();
            }
            unlock();
        }
        return m_Instance;
    }

private:
    Singleton();
    static Singleton *m_Instance;
};
Singleton *Singleton::m_Instance = NULL;
View Code
  • 内部静态实例的懒汉
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        //C++0X以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。
        lock();
        static Singleton m_Instance;
        unlock();
        return &m_Instance;
    }

private:
    Singleton();
};
View Code

二、饿汉式

饿汉式的特点是提前构造,以空间换时间。

(注:如果配合工厂模式使用,饿汉式会缓存多个实例,需要考虑效率问题)

class Singleton
{
public:
    static Singleton *GetInstance()
    {
        //此处去除const属性是为了可以调用Set方法
        return const_cast <Singleton *>(m_Instance);
    }

private:
    Singleton();
    static const Singleton *m_Instance;
};
//静态成员初始化在main()函数调用之前执行,故不会出现线程安全问题
const Singleton *Singleton::m_Instance = new Singleton();
View Code

【隐蔽的坑】:如果在该类构造函数中调用另外一个相同实现的单例的方法则可能出现问题,原因是此时另一个单例对象可能还未构造成功。

扩展问题

    KDL problem是指对于有复杂依赖关系的多个Singleton,如何解决最后析构顺序的问题。
    某个程序中使用了如下3个Singleton:Keyboard,Display,Log。
    Keyboard和Display分别对应于计算机的键盘和显示器,Log用来记录错误信息。假设当Keyboard和Display的构造函数和析构函数出现错误时会调用Log记录错误信息,并且构造和析构导致的任何错误都会终止程序。在程序启动时,如果Keyboard构造成功,Display构造失败,很显然在Display的构造函数中将会构造Log而且失败信息会被Log记录,根据假设这时候程序准备退出,假设按LIFO的顺序销毁各单例。因为Keyboard先于Log构造,所以Log先于Keyboard析构,但是当由于某种原因Keyboard在析构时失败,想要调用Log记录错误信息时,Log早已被销毁,则Log::Instance()将会导致未定义行为。
KDL problem

说明:

  1. 以上几种实现均未考虑对象释放,当涉及文件锁,数据库连接等,需要我们主动释放的,可以利用私有内嵌类(保证其析构不会在类外被调用),在单例类中声明一个私有的静态的内嵌类对象,并在内嵌类的公有的析构函数中释放;
  2. 未考虑拷贝构造和赋值操作符,应该将它们设为私有(保证友元也使用不了),且只声明不实现,这样如果代码中调用了拷贝构造或者赋值操作,编译时就会报错;
  3. 关于上面提到的【隐蔽的坑】,提供一个实现避免这种情况
    #include "iostream"
    using namespace std;
    
    class SingletonA
    {
    public:
        static SingletonA* GetInstance()
        {
            cout << "SingletonA::GetInstance" << endl;
            static SingletonA m_Instance;
            return &m_Instance;
        }
        void doSomething() { cout << "SingletonA::doSomething" << endl; }
    private:
        SingletonA()
        {
            cout << "SingletonA" << endl;
        }
        class InsideClass
        {
        public:
            InsideClass()
            {
                cout << "SingletonA::InsideClass" << endl;
                SingletonA::GetInstance();
            }
        };
        static InsideClass m_InA;
    };
    
    class SingletonB
    {
    public:
        static SingletonB* GetInstance()
        {
            cout << "SingletonB::GetInstance" << endl;
            static SingletonB m_Instance;
            return &m_Instance;
        }
    private:
        SingletonB() 
        { 
            cout << "SingletonB" << endl; 
            SingletonA::GetInstance()->doSomething();
        }
        class InsideClass
        {
        public:
            InsideClass()
            {
                cout << "SingletonB::InsideClass" << endl;
                SingletonB::GetInstance();
            }
        };
        static InsideClass m_InB;
    };
    
    SingletonA::InsideClass SingletonA::m_InA;
    SingletonB::InsideClass SingletonB::m_InB;
    
    int main()
    {
        SingletonB::GetInstance();
    
        system("pause");
        return 0;
    }
    
    /*输出结果:
    SingletonA::InsideClass
    SingletonA::GetInstance
    SingletonA
    SingletonB::InsideClass
    SingletonB::GetInstance
    SingletonB
    SingletonA::GetInstance
    SingletonA::doSomething
    SingletonB::GetInstance
    */
    View Code

三、登记式

待续……

 

相关参考:

较好的博文:http://www.jellythink.com/archives/82

大牛写的单例模板:https://github.com/chenshuo/muduo/blob/master/muduo/base/Singleton.h

posted @ 2016-05-27 02:29  3th1nk  阅读(99)  评论(0)    收藏  举报