zoukankan      html  css  js  c++  java
  • 游戏设计模式——C++单例类

    前言:

    本文将探讨单例类设计模式,单例类的懒汉模式/饿汉模式,单例类的多线程安全性,最后将利用C++模板减少单例类代码量。

    本文假设有一个Manager管理类,并以此为探究单例类的设计模式。


    懒汉模式

    懒汉模式:顾名思义,是一种典型的拖延(lazy)策略。当第一次要用单例类的时候,再产生实例。

    类声明:

    class Manager{
    public:
    ~Manager();
    //提供单例对象访问
    static Manager* getInstance();
    //删除单例对象
    static void deleteInstance();
    void dosomething();
    protected: //构造函数声明为 保护方法 Manager(); //单例对象指针 static Manager* s_Manager; };
    //单例对象指针初始化为nullptr,防止指向了未定义的数据
    Manager* Manager::s_Manager = nullptr;
     
    //提供单例类对象访问
    Manager* Manager::getInstance(){
       //当没有存在实例时(一般是指准备第一次用)时,才生成新实例
       if(!s_Manager)s_Manager = new CacheManger();
       return s_Manager;
    }
     
    //删除单例类
    void Manager::deleteInstance(){
       if(s_Manager){
       deleted s_Manager;
       s_Manager = nullptr;//别忘了赋予空指针,否则指向未定义数据
       }
    }
     
    void Manager::dosomething(){
       //dosometing
    }

    这样我们就能在平时的程序用

    Manager::getInstance()->dosomething();

    来运用单例类来做某些操作了。

    懒汉模式with线程安全

    但是上面的例子,并不能保证线程安全。

    假如没有实例时,然后某两个线程都几乎同时使用getInstance(),那么很可能会产生2份实例,其中一份还会变成泄露的内存。

    为了解决线程安全问题,自然想到用锁:(本文使用了C++11 <mutex>的std::mutex作为互斥锁,在类额外增加了一个静态变量std::mutext s_mtx;)

    //提供单例类对象访问
    Manager* Manager::getInstance() {
        if (!s_Manager) {
            //上锁
            std::lock_guard<std::mutex> lock(s_mtx);
            //当没有存在实例时(一般是指准备第一次用)时,才生成新实例
            if (!s_Manager)
            {
                s_Manager = new Manager();
            }
            //解锁
        }
        return s_Manager;
    }
    
    //删除单例类
    void Manager::deleteInstance() {
        if (s_Manager) {
            //上锁
            std::lock_guard<std::mutex> lock(s_mtx);
            if (s_Manager) {
                delete s_Manager;
                //别忘了赋予空指针,否则指向未定义的数据
                s_Manager = nullptr;
            }
            //解锁
        }
    }

    为什么不是(上锁,检查,操作,解锁)或者(检查,上锁,操作,解锁),而是使用了双重检查(检查,上锁,检查,操作,解锁)?

    1. 上锁的成本远远比检查空指针要高,且当需要产生实例时才需要锁操作。而实际上大量多次使用getInstance时(因为已经产生了实例)并不需要上锁,若先上锁,则会严重造成性能阻塞。
    2. 仅仅是检查后再上锁,则根本没有做到任何线程安全。

    饿汉模式

    饿汉模式与懒汉模式相反,是程序一开始就生成唯一实例。这样就不用检查是否存在实例,而且也无需考虑产生实例时的线程安全。

    class Manager {
    public:
        ~Manager();
        //提供单例对象访问
        static Manager& getInstance();
        void dosomething();
    protected:
        //构造函数声明为 保护方法
        Manager();
        //单例对象指针
        static Manager s_Manager;
    };
    
    //提供单例类对象访问
    Manager& Manager::getInstance(){
        return s_Manager;
    }

    使用方法:

    Manager::getInstance().dosomething();

     可以看到代码比懒汉模式简单多了。

    在大量使用检查空指针造成的性能瓶颈而内存始终充足时,可以考虑使用饿汉模式

    Meyers Singleton(目前最推荐的C++单例写法)

    class Singleton {
    public:
      static Singleton& Instance() {
        static Singleton theSingleton;
        return theSingleton;
      }
    private:
      Singleton();
      Singleton(Singleton const&);
      Singleton& operator=(Singleton const&); 
      ~Singleton(); 
    };

    这段代码很简单,虽然看上去和懒汉模式类似,只是static变量的位置从类移动到了实例获取函数内。

    但是实际上由于C++的机制,当第一次调用该函数时,实例才会被构建出来。

    这样既可以得到饿汉模式的线程安全,又可以有懒汉模式的按需分配的功能。

    应用场景注意

     单例类设计模式算是比较经典的一个模式,但是需要注意,它并不是想象中那么美好。

    1. 它是一种换皮的全局变量。
    2. 它促进了耦合。
    3. 它可能对并发不友好(取决于你使用的单例写法)。

    一些替代方案:

    1. 当你仅需要全局可见的方法时,应该用类静态方法而不是一个类实例。
    2. 尽可能为实例提供其它便捷的访问方式(传参/基类获取/服务定位器获取等),而不是通过提供全局可见的访问方式。
    3. 如果你只是需要类保证只有唯一对象而不需要全局性,那么应对外封闭获取实例接口(其实就是不可全局获取实例的)。

    所以要注意单例类设计模式不应被泛用,通过上面的替代方案多多少少也就减少了很多不必要的单例设计。


    游戏设计模式系列-其他文章:

    https://www.cnblogs.com/KillerAery/category/1307176.html

  • 相关阅读:
    1>/dev/null 2>&1的含义
    rpm常用命令及rpm参数介绍
    linux按位运算
    关于比较运算符的一个例子
    js屏蔽效果
    jquery异步提交无刷新
    常用js验证
    获取输入字符的首字母(中文为拼音首字母)
    SQL查询合并字符串
    获取鼠标点击的坐标处理
  • 原文地址:https://www.cnblogs.com/KillerAery/p/9097529.html
Copyright © 2011-2022 走看看