• 设计模式--单例模式


    单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

    实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

    上面是引用维基百科上的定义。                                                                      

    既然单例模式只允许存在一个对象,那么对象的拷贝,赋值就都是不允许的,因此把拷贝构造函数、赋值操作符全部声明为private

    第一种实现

    class Singleton
    {
     private:
         static Singleton *m_instance;
         Singleton(){}//隐藏构造函数
         Singleton(const Singleton &){}//隐藏拷贝构造函数
         Singleton& operator=(const Singleton &a){}//隐藏赋值操作符
         ~Singleton(){}//隐藏析构函数
     public:
        static Singleton *getInstance()
        {
            if(m_instance == NULL)
                m_instance = new Singleton;
            return m_instance;
        }
        
    };
    Singleton * Singleton::m_instance = NULL;

    这种实现很明显满足单例模式的要求,但是有两个问题 (1)我们new的对象没有被delete,(2)这种设计不是线程安全的(两个线程可能同时判断m_instance == NULL,这样就有两个实例被创建了),为了解决上面的问题,第二种实现如下


    第二种实现

    class Singleton
    {
     private:
         static Singleton *m_instance;
         Singleton(){}//隐藏构造函数
         Singleton(const Singleton &){}//隐藏拷贝构造函数
         Singleton& operator=(const Singleton &a){}//隐藏赋值操作符
         ~Singleton(){}//隐藏析构函数
         class DelInstance
         {
         public:
            ~DelInstance()
            {
                if(Singleton::m_instance)
                    delete Singleton::m_instance;
            }
         };
         static DelInstance delIns; //负责回收new出来的对象;
    
     public:
        static Singleton *getInstance()
        {
            if(m_instance == NULL)
            {
                lock(); //加锁(lock 和 unlock是随便写的函数,c++本身没有)
                if(m_instance == NULL)
                    m_instance = new Singleton;
                unlock();//释放锁
            }
            return m_instance;
        }
    
    };
    Singleton::DelInstance Singleton::delIns;
    Singleton * Singleton::m_instance = NULL;

    这里我们使用了另一个私有内嵌类DelInstance,在Singleton内定义了一个静态的的对象delIns来负责回收new出来的对象,因为静态对象delIns在程序结束时会自动调用自己析构函数从而释放m_instance指向的内存,这里新手可能会犯两个错误:

    1、不使用额外的类,直接把delete语句写在singleton的析构函数中。这种做法是错误的,因为我们是通过new出来的singleton实例,程序结束时不会自动调用析构函数,再者如果真的调用了就会进入一个无限循环的状态,即singleton的析构函数是为了删除一个singleton对象,删除该对象时,又会调用singleton的析构函数,这样一直循环下去。

    2、delIns成员不定义成static。这也是错误的,如果delIns不是static,那么delIns就要等到singleton对象析构时才会析构,但是delIns的目的就是要析构singleton,这就矛盾了。如果delIns是static的,他的生命期和他所在的类对象没有关系,他相当于是全局的,当他的生命期到的时候就会自动析构,从而析构singleton。

    getInstance中我们使用了double-check来保证线程安全,只有当m_instance是NULL时,线程才会加锁。这样也保证了只创建了一个对象实例。

    这是一种懒汉模式,即等到需要时才创建对象的实例。                                                                                 本文地址


    第三种实现

    class Singleton
    {
     private:
         static Singleton *m_instance;
         Singleton(){}//隐藏构造函数
         Singleton(const Singleton &){}//隐藏拷贝构造函数
         Singleton& operator=(const Singleton &a){}//隐藏赋值操作符
         ~Singleton(){}//隐藏析构函数
         class DelInstance
         {
         public:
            ~DelInstance()
            {
                if(Singleton::m_instance)
                    delete Singleton::m_instance;
            }
         };
         static DelInstance delIns; //负责回收new出来的对象;
    
     public:
        static Singleton *getInstance()
        {
            return m_instance;
        }
    
    };
    Singleton::DelInstance Singleton::delIns;
    Singleton * Singleton::m_instance = new Singleton;

    这是属于饿汉模式,即一开始就创建一个类的实例,以后都返回其地址,是线程安全的。


    第四种实现

    class Singleton
    {
     private:
         static Singleton s;
         Singleton(){}//隐藏构造函数
         Singleton(const Singleton &){}//隐藏拷贝构造函数
         Singleton& operator=(const Singleton &a){}//隐藏赋值操作符
         ~Singleton(){}//隐藏析构函数
     public:
        static Singleton *getInstance()
        {
            return &s;
        }
        
    };
    
    Singleton Singleton::s;

    饿汉模式,这种实现定义一个私有的静态对象实例,(注意不能在类中定义自身的非静态对象,因为这样会形成无限循环定义,而静态对象因为保证只有一个副本,因此不会循环定义),这也是线程安全的


    第五种实现

    class Singleton
    {
     private:
         Singleton(){}//隐藏构造函数
         Singleton(const Singleton &){}//隐藏拷贝构造函数
         Singleton& operator=(const Singleton &a){}//隐藏赋值操作符
         ~Singleton(){}//隐藏析构函数
     public:
        static Singleton *getInstance()
        {
            lock();//c++11 可以不用加锁
            static Singleton s;
            unlock();
            return &s;
        }
    
    };

    懒汉模式,这种实现只有当第一次调用getInstance时定义局部静态变量s,以后直接返回。c++11之前需要加锁,因为变量的初始化操作不是原子操作,不加锁就不是线程安全的的;c++11则不用加锁,因为c++11保证:如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待完成该变量完成初始化(见here)。


    注意

    对于第一、二、三、四种方式,都有可能导致static initialization order fiasco (可参考here)。因为c++中,在全局或名字空间范围内的全局变量,在一个类中被声明为static,或,在一个文件范围被定义为static。这三种变量统称“非局部静态对象”,这三种对象的初始化顺序“C++”未作明确规定。因此如果有个类在构造函数中调用了getInstance:

    struct Foo {
        Foo() {
            Singleton::getInstance();
        }
      };
      Foo foo;
    不能保证foo初始化时,m_instance已经初始化
    【版权声明】转载请注明出处:http://www.cnblogs.com/TenosDoIt/p/3639395.html
  • 相关阅读:
    HTTP协议【详解】——经典面试题
    原生JS的地区二级联动,很好理解的逻辑
    js操作字符串的常用方法
    移除input框type="number"在部分浏览器的默认上下按钮
    atom
    解决gitHub下载速度慢的问题
    ATOM常用插件推荐
    脚踝扭伤肿了怎么办
    这才是真正的电子科大
    月入 7000,怎么存钱?
  • 原文地址:https://www.cnblogs.com/TenosDoIt/p/3639395.html
Copyright © 2020-2023  润新知