zoukankan      html  css  js  c++  java
  • c++的单例模式及c++11对单例模式的优化

    单例模式

    单例模式,可以说设计模式中最常应用的一种模式了,据说也是面试官最喜欢的题目。但是如果没有学过设计模式的人,可能不会想到要去应用单例模式,面对单例模式适用的情况,可能会优先考虑使用全局或者静态变量的方式,这样比较简单,也是没学过设计模式的人所能想到的最简单的方式了。

    一般情况下,我们建立的一些类是属于工具性质的,基本不用存储太多的跟自身有关的数据,在这种情况下,每次都去new一个对象,即增加了开销,也使得代码更加臃肿。其实,我们只需要一个实例对象就可以。如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响。

    考虑到这些需要,我们将默认的构造函数声明为私有的,这样就不会被外部所new了,甚至可以将析构函数也声明为私有的,这样就只有自己能够删除自己了。在Java和C#这样纯的面向对象的语言中,单例模式非常好实现,直接就可以在静态区初始化instance,然后通过getInstance返回,这种就被称为饿汉式单例类。也有些写法是在getInstance中new instance然后返回,这种就被称为懒汉式单例类,但这涉及到第一次getInstance的一个判断问题。

    下面的代码只是表示一下,跟具体哪种语言没有关系。

    单线程中:

    Singleton* getInstance()
    {
        if (instance == NULL)
            instance = new Singleton();
     
        return instance;
    }

    这样就可以了,保证只取得了一个实例。但是在多线程的环境下却不行了,因为很可能两个线程同时运行到if (instance == NULL)这一句,导致可能会产生两个实例。于是就要在代码中加锁。

    Singleton* getInstance()
    {
        lock();
        if (instance == NULL)
        {
           instance = new Singleton();
        }
        unlock();
    
        return instance;
    }

    但这样写的话,会稍稍映像性能,因为每次判断是否为空都需要被锁定,如果有很多线程的话,就爱会造成大量线程的阻塞。于是出现了双重锁定。

    Singleton* getInstance()
    {
        if (instance == NULL)
        {
        lock();
            if (instance == NULL)
            {
                   instance = new Singleton();
            }
            unlock();
        }
    
        return instance;
    }

    这样只够极低的几率下,通过越过了if (instance == NULL)的线程才会有进入锁定临界区的可能性,这种几率还是比较低的,不会阻塞太多的线程,但为了防止一个线程进入临界区创建实例,另外的线程也进去临界区创建实例,又加上了一道防御if (instance == NULL),这样就确保不会重复创建了。

    常用的场景

    单例模式常常与工厂模式结合使用,因为工厂只需要创建产品实例就可以了,在多线程的环境下也不会造成任何的冲突,因此只需要一个工厂实例就可以了。

    优点

    1.减少了时间和空间的开销(new实例的开销)。

    2.提高了封装性,使得外部不易改动实例。

    缺点

    1.懒汉式是以时间换空间的方式。(上面使用的方式)

    2.饿汉式是以空间换时间的方式。(下面使用的方式)

    #ifndef _SINGLETON_H_
    #define _SINGLETON_H_
    
    
    class Singleton{
    public:
        static Singleton* getInstance();
    
    private:
        Singleton();
        //把复制构造函数和=操作符也设为私有,防止被复制
        Singleton(const Singleton&);
        Singleton& operator=(const Singleton&);
    
        static Singleton* instance;
    };
    
    #endif
     
    
    #include "Singleton.h"
    
    
    Singleton::Singleton(){
    
    }
    
    
    Singleton::Singleton(const Singleton&){
    
    }
    
    
    Singleton& Singleton::operator=(const Singleton&){
    
    }
    
    
    //在此处初始化
    Singleton* Singleton::instance = new Singleton();
    Singleton* Singleton::getInstance(){
        return instance;
    }
     
    
    #include "Singleton.h"
    #include <stdio.h>
    
    
    int main(){
        Singleton* singleton1 = Singleton::getInstance();
        Singleton* singleton2 = Singleton::getInstance();
    
        if (singleton1 == singleton2)
            fprintf(stderr,"singleton1 = singleton2
    ");
    
        return 0;
    }

    以上使用的方式存在问题:只能实例化没有参数的类型,其它带参数的类型就不行了。

    c++11 为我们提供了解决方案:可变模板参数

      

    template <typename T>
    class Singleton
    {
    public:
    template<typename... Args>
      static T* Instance(Args&&... args)
      {
            if(m_pInstance==nullptr)
                m_pInstance = new T(std::forward<Args>(args)...);
            return m_pInstance;
        }
      static T* GetInstance()
          {
                if (m_pInstance == nullptr)
                      throw std::logic_error("the instance is not init, please initialize the instance first");
                return m_pInstance;
          }
    static void DestroyInstance()
        {
            delete m_pInstance;
            m_pInstance = nullptr;
        }
    
    private:
            Singleton(void);
            virtual ~Singleton(void);
            Singleton(const Singleton&);
            Singleton& operator = (const Singleton&);
    private:
        static T* m_pInstance;
    };
    
    template <class T> T*  Singleton<T>::m_pInstance = nullptr;

    由于原来的接口中,单例对象的初始化和取值都是一个接口,可能会遭到误用,更新之后,讲初始化和取值分为两个接口,单例的用法为:先初始化,后面取值,如果中途销毁单例的话,需要重新取值。如果没有初始化就取值则会抛出一个异常。

    Multiton的实现

    #include <map>
    #include <string>
    #include <memory>
    using namespace std;
    
    template < typename T, typename K = string>
    class Multiton
    {
    public:
        template<typename... Args>
        static std::shared_ptr<T> Instance(const K& key, Args&&... args)
        {
            return GetInstance(key, std::forward<Args>(args)...);
        }
    
        template<typename... Args>
        static std::shared_ptr<T> Instance(K&& key, Args&&... args)
        {
            return GetInstance(key, std::forward<Args>(args)...);
        }
    private:
        template<typename Key, typename... Args>
        static std::shared_ptr<T> GetInstance(Key&& key, Args&&...args)
        {
            std::shared_ptr<T> instance = nullptr;
            auto it = m_map.find(key);
            if (it == m_map.end())
            {
                instance = std::make_shared<T>(std::forward<Args>(args)...);
                m_map.emplace(key, instance);
            }
            else
            {
                instance = it->second;
            }
    
            return instance;
        }
    
    private:
        Multiton(void);
        virtual ~Multiton(void);
        Multiton(const Multiton&);
        Multiton& operator = (const Multiton&);
    private:
        static map<K, std::shared_ptr<T>> m_map;
    };
    
    template <typename T, typename K>
    map<K, std::shared_ptr<T>> Multiton<T, K>::m_map;

     

  • 相关阅读:
    Python3之random模块常用方法
    Go语言学习笔记(九)之数组
    Go语言学习笔记之简单的几个排序
    Go语言学习笔记(八)
    Python3之logging模块
    Go语言学习笔记(六)
    123. Best Time to Buy and Sell Stock III(js)
    122. Best Time to Buy and Sell Stock II(js)
    121. Best Time to Buy and Sell Stock(js)
    120. Triangle(js)
  • 原文地址:https://www.cnblogs.com/kefeiGame/p/7376207.html
Copyright © 2011-2022 走看看