首页 > 代码库 > 单例模式

单例模式

1. 由单例模式中关于static的问题

2. 为什么要有单例模式

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

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

 

1. 由单例模式中关于static的问题:“静态成员函数调用了非静态成员函数(构造函数)”

最简单的单例模式如下:

 1 class Singleton {
 2 private:
 3     Singleton() {};
 4     ~Singleton() {};
 5 public:
 6     static Singleton* getInstance()
 7     {
 8         if (_instance == nullptr)
 9         {
10             _instance = new Singleton;
11         }
12         return _instance;
13     }
14     static void deleteInstance()
15     {
16         if (_instance != nullptr)
17         {
18             delete _instance;
19             _instance = nullptr;
20         }
21     }
22 private:
23     static Singleton* _instance;
24 };
25 Singleton* Singleton::_instance = nullptr;

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

  首先分析下这种常识背后的原因:普通成员函数(非静态)在编译器编译的时候会在函数参数上多加上一个this指针的参数,而静态成员函数则没有。因此如果在静态成员函数中调用了非静态成员函数,this指针无从而来。

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

  同时调用静态函数 Singleton::getInstance() 时,表明了作用域在 Singleton:: 内,那么就可以在这个函数里访问Singleton类里任何访问权限的成员。

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

 

 

2. 为什么要有单例模式

  单例模式实现了 某类只能有一个实例,并且提供一个全局访问点。上述的代码中可以看到,这个唯一的实例,就作为此类的静态数据成员存在。getInstance就是全局访问点接口。(完整来说 除了私有化构造函数,还需要私有拷贝构造函数(其实就是构造函数一种)、赋值操作符。)

  那么要实现只有一个实例,也可以用全局变量,大家都通过这个全局变量来访问操作。但是全局变量的缺点是,不管代码中实际用到与否,都会初始化。但是单例模式中的唯一实例只有真正用到的时候才会被创建,即调用getInstance时。

 

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

在生成实例的时候加锁:

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

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

技术分享
Class Singleton
{
    ....
    Singleton* getInstance()
    {
        return _instance;
    }
    ....
}
Singleton* Singleton::_instance = new Singleton;
View Code

 

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

模板类的单例模式如下:

template<typename T>
class Singleton
{
protected:                        // protected确保子类可以创建对象
    Singleton() {};
    virtual ~Singleton() {};      // 如果析构函数里面有资源的释放什么的 就必须写上 virtual
public:
    static T* getInstance()
    {
        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>;  // 这样基类中才能访问到派生类的私有的构造函数    


};

 

这里有几个问题:

1. CRTP递归模板,用一句话定义:  使用派生类 作为 模板参数 特化 基类

2.

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

static T _instance;

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

3.

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

T* _instance;

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

4.

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

T _instance;

CRTP这种模式可以么?不可以,因为T还没有定义出来。

 

参考:

《headfirst设计模式》

 项目代码

单例模式