ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

单例模式以及一些思考

2019-04-29 16:50:49  阅读:223  来源: 互联网

标签:Singleton 单例 getInstance 模式 instance static 思考 函数


单例模式的特点

   该类只能有一个实例,并提供一个全局访问点。

 

1. 简单的单例模式(C++)如下:

1)私有化默认构造函数(还需要私有拷贝构造函数(其实就是构造函数一种)、赋值操作符) 以及 析构函数。

2)static成员函数getInstance 提供全局访问点 

3)static成员变量_instance 只能有一个实例

class Singleton {
private:
    Singleton() {};
    ~Singleton() {};
public:
    static Singleton* getInstance() //全局访问点接口
    {
        if (_instance == nullptr)
        {
            _instance = new Singleton;
        }
        return _instance;
    }
    static void deleteInstance()
    {
        if (_instance != nullptr)
        {
            delete _instance;
            _instance = nullptr;
        }
    }
private:
    static Singleton* _instance;  // 只能有一个实例
};
Singleton* Singleton::_instance = nullptr;

这里想到一个问题:在第10行,new 了一个Singleton,此时调用了Singleton的构造函数,看上去是不是“静态成员函数调用了非静态成员函数(构造函数)”? (但是静态成员函数只能调用静态成员函数。这不是矛盾了么?)

  首先那么为什么静态函数不能调用非静态函数?因为静态函数没有this指针,而普通成员函数(非静态)需要this指针。

  再看那个问题,在new Singleton的时候第一步 分配了一段内存,也就有了这个实例的this指针,第二步再调用构造函数,此时需要的this指针就是第一步创建的。也就是说对这个新的对象 调用他的静态和非静态成员函数(当然是要有访问权限的函数)都是可以的,因为在调用非静态成员函数时的this指针是这个新的对象实例的地址,而不是需要通过函数getInstance参数传进来this指针。只不过问题中正好是生成的是一个本类的实例。同时调用静态函数 Singleton::getInstance() 时,表明了作用域在 Singleton:: 内,那么就可以在这个函数里访问Singleton类里任何访问权限的成员。

 

注意:静态数据成员需要在类外进行定义,因为如果没有定义就是没有分配内存(类内的仅仅是声明而已),没有分配内存怎么可以访问呢。

 

2. 单例模式 vs 全局变量

    单例模式实现了 某类只能有一个实例,并且提供一个全局访问点。

  要实现只有一个全局访问点,也可以用全局变量。全局变量的缺点是,不管代码中实际用到与否,都会初始化。但是单例模式可以采用懒汉式的方法,用到再初始化。(单例模式 vs 静态类 )

 

 

3. 多线程安全的单例模式

生成实例的时候加锁

static Singleton* getInstance()
{
     if (_instance == nullptr)
    {
        加锁操作
        if (_instance == nullptr)   // 想想这里为什么还要再判断一次
            _instance = new Singleton;
    }
     return _instance;
 }

 也可以在初始化的时候直接new, getInstance直接返回对象。不过这样就失去了他的一个优点—只有真正用到的时候才会被创建。

Class Singleton
{
    ....
    Singleton* getInstance()
    {
        return _instance;
    }
    ....
}
Singleton* Singleton::_instance = new Singleton;

 

4. 模版类单例模式的实现

模板类的单例模式如下:(就是把1中的单例模式模版化)

template<typename T>
class Singleton
{
protected:                        // protected确保子类可以创建对象
    Singleton() {};
    virtual ~Singleton() {};      // 如果析构函数里面有资源的释放什么的 就必须写上 virtual
public:
    static T* getInstance()   // 如果getInstance是protected就是只有继承体系中才能获得使用,不是全局访问。
    {
        if (_instance == nullptr)
        {
            _instance = new T;
        }
        return _instance;
    }
    static void deleteInstance()
    {
        if (_instance != nullptr)
        {
            delete _instance;
            _instance = nullptr;
        }
    }
private:
    static T* _instance;

};
template<typename T>
T* Singleton<T>::_instance = nullptr;

之所以抽象一个模板类的单例模式就是为了复用代码。

只需要把 想要单例化的类 继承自 以自己作为模板参数的Singleton就可以,比如要单例化SingletonA:

// 实例化模板参数, CRTP递归模板
class SingletonA:public Singleton<SingletonA>
{
    //...一些其他的接口
private:  //(跟项目中代码中不同,项目中把构造函数变成了public 失去了单例的意义  尽管使用的时候都按照约定的 getInstance函数。)
    SingletonA() {};
    ~SingletonA() {};
  friend Singleton<SingletonA>;  // 这样基类中才能访问到派生类的私有的构造函数    


};
// 注意:如果某个类B 继承了SingletonA, B.getInstance()返回的还是 SingletonA的实例。

CRTP递归模板的几个问题:

1). 概念:  使用派生类 作为 模板参数 特化 基类

 

2).如果基类中的数据成员为:

static T _instance;

(现在是指针)

CRTP这种模式可以么?可以,因为静态数据成员不占类的内存空间。

 

3).如果基类中的数据成员为:

T* _instance;

(现在是静态的)

CRTP这种模式可以么?可以,因为指针占固定的大小。

 

4).如果基类中的数据成员为:

T _instance;

CRTP这种模式可以么?

不可以,因为T还没有定义出来。

 

 

5. 使用单例模式需要注意的问题:

单例本质上是全局状态——它只是被封装在一个类中。他拥有全局变量造成的所有问题: 

  1). 理解代码成本,必须搜索,哪里修改过他,有没有改错。

  2). 增加耦合性, 因为全局可用。

  3). 对并行不友好:当我们创建了全局变量,一个所有线程都能过访问和修改的内存。当其中一个使用的时候,可能其他线程正在使用那块内存。

在盲目使用具有全局作用域的单例对象之前,考虑有哪些其他访问对象的途径:

  1) 可以讲对象作为参数传入函数。比如常用的渲染物体函数的 输入参数:图形设备对象context。

  2)从基类中获得。比如一个static变量在基类中定义。那么任何派生类都可以使用。这保证了继承体系之外的对象不能访问这个变量

  3)从已经是全局的东西中获取。用一个单例类 封装 一大推本来需要想要做成单例的类。

 

实现某类只实现一个实例,但是不提供全局访问点:

这个类的创建 具有作用域

class FileSystem
{
public:
  FileSystem()
  {
    assert(!instantiated_);
    instantiated_ = true;
  }

  ~FileSystem() { instantiated_ = false; }

private:
  static bool instantiated_;
};

bool FileSystem::instantiated_ = false;
FileSystem fileSys;   // 可以创建

FileSystem fileSys1;    // 创建失败

 

游戏中使用单例模式:"在真正使用的时候才初始化"的危害

比如一个音频系统的单例:

   1)如果初始化音频系统消耗了几百个毫秒,我们需要控制它何时发生。 如果在第一次声音播放时才初始化它自己,这初始化有可能发生游戏的高潮,导致可见的掉帧和断续的游戏体验。

  2) 如果初始化音频系统时需要在堆上分配内存,我们需要知道何时初始化发生了。因为 游戏通常需要严格管理在堆上分配的内存来避免碎片。在一开始的时候就初始化音频系统,我们就知道何时初始化发生了, 这样我们可以控制内存待在堆的哪里。

所以我们选择在一开始的时候就初始化实例。

 

游戏中的许多的单例模式都是管理器。但是管理器真的需要存在么?

是不是可以把 管理器里的函数 放到 管理器 管理的类 的本身中?

I’ve seen codebases where it seems like every class has a manager: Monster, MonsterManager, Particle, ParticleManager, Sound, SoundManager, ManagerManager. Sometimes, for variety, they’ll throw a “System” or “Engine” in there, but it’s still the same idea. 但是这些类真的都需要一个管理器么?

Poorly designed singletons are often “helpers” that add functionality to another class. If you can, just move all of that behavior into the class it helps. After all, OOP is about letting objects take care of themselves.

 

 

 

参考:

《headfirst设计模式》

《游戏编程模式》http://gpp.tkchu.me/singleton.html#为什么我们后悔使用它

标签:Singleton,单例,getInstance,模式,instance,static,思考,函数
来源: https://blog.csdn.net/u012138730/article/details/89674330

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有