浅谈C++设计模式--单例模式

单例模式(Singleton)[分类:建立型] [目的:为程序的某个类的访问,提供惟一对象实例]html

这估计是设计模式里面最简单的一个类了,下面咱们一块儿看看,单例它是什么?以及如何实现一个单例git

  • 基本定义
    • 保证一个类仅有一个实例,并提供一个访问它的全局访问点.
  • 我的理解
    • 就是一个类在整个程序里面,有且仅有一个实例,这个实例由该类本身负责建立和保存,这样保证它不会在任何其余地方被建立,必须提供一个访问这个实例的全局访问接口.
    • 那么有些人可能有疑问,为何不直接用一个全局变量来控制外部对它的访问呢?在这里若是用全局变量来控制外部的访问它实际上仍是不能避免你实例化多个对象,这样仍是达不到单例有且仅有一个实例的目的。
  • 单例的特征
    1. 有且仅有一个实例
    2. 实例必须由本身建立和保存
    3. 要提供和给外部访问实例的接口
  • UML结构图

 

  • 从UML结构图中咱们能够一步步实现出如下代码
  • 简单版本的单例模式
 1 class Singleton
 2 {
 3 public:
 4     static Singleton& GetInstance() 
 5     {
 6         static Singleton instance;
 7         return  instance;
 8     }
 9 private:
10     Singleton();
11 
12 };
  • 这里咱们能够看到,这个版本构造函数被私有化了,所以外部没法直接调用单例的构造器,并且使用静态的局部变量作返回,这样不须要考虑对内存的管理,是否是很是方便!可是咱们在对函数执行如下操做时
1 Singleton instance1=Singleton::GetInstance(); 
2 Singleton instance2=Singleton::GetInstance();
3 qDebug()<<"instance1 address:"<<&instance1;
4 qDebug()<<"instance2 address:"<<&instance2;
  • 咱们运行程序看结果

  •  在这里instance1和instance2实例他们的地址是彻底不一样的.这样的作法是不可取的,不是真正的单例,那么如何避免这样的问题的?跟开发者说大家直接调用GetInstance接口就行。
  • NONONO....
  • 下面咱们看看修改版本的单例看看 
 1 class SingletonModify
 2 {
 3 public:
 4     static SingletonModify& GetInstance() 
 5     {
 6         static SingletonModify instance;
 7         return  instance;
 8     }
 9     void sayHello();
10 private:
11     SingletonModify();
12     SingletonModify(SingletonModify const &);//to use this modify wrong demo
13     SingletonModify& operator =(SingletonModify const &);//to use this modify wrong demo
14 };
  • 从上面咱们能够看出,咱们从新定义了构造器以及=操做,这里不须要实现。而后咱们再用相同的方式测试看看,这下编译器编译不经过了,那咱们想调用实例的方法或者操做实例怎么办呢?
SingletonModify::GetInstance().sayHello();//after modify code,
  • 那咱们只能经过上述方法访问实例了,保证了实例的惟一性了。
  • 简单版本的单例模式技术总结
  1. 将构造器私有化(保证外部没法建立实例) 
  2. 使用静态的局部变量做为返回(无需考虑内存管理的问题)
  3. 从新定义赋值运算符以及构造器.(避免=发生拷贝操做)
  • 是否是以为到这里单例就已经差很少啦?并非,咱们使用的是C++,可是并无使用到C++的一个重要特征:指针
  • 首先咱们针对上述简单版本的单例模式再次进行修正,咱们修改单例的函数返回,用指针接收,看看下面代码
 1 class SingletonSimplify
 2 {
 3 public:
 4     SingletonSimplify();
 5     static SingletonSimplify* GetInstanceptr()//simplify method
 6     {
 7         static SingletonSimplify instance;
 8         return  &instance;
 9     }
10 };
  • 测试:
1 SingletonSimplify* instanceSmp1=SingletonSimplify::GetInstanceptr();//another simplify singleton method
2 SingletonSimplify* instanceSmp2=SingletonSimplify::GetInstanceptr();
3 qDebug()<<"instanceSmp1 address:"<<instanceSmp1;
4 qDebug()<<"instanceSmp2 address:"<<instanceSmp2;

 

 

  •  咱们能够发现,用指针接收以后2个指针都是指向同一个地址,很好,这也是一种单例的实现方式,可是这个也属于访问静态局部对象,仍是没有真正意义上使用到指针.再看看下面两种方式
  1. 懒汉模式
    • 先看头文件.h代码
      •  1 class SingletonLazyMode
         2 {
         3 public:
         4     static SingletonLazyMode* GetInstance();
         5     static void InstanceDispose();
         6     void sayHi();
         7 private:
         8     SingletonLazyMode();
         9     static SingletonLazyMode* mSingletonInstance;
        10     int num=10;
        11 };
    • .cpp实现
      •  1 SingletonLazyMode *SingletonLazyMode::mSingletonInstance=NULL;
         2 SingletonLazyMode *SingletonLazyMode::GetInstance()
         3 {
         4     if(mSingletonInstance==NULL)
         5     {
         6         mSingletonInstance=new SingletonLazyMode();
         7         mSingletonInstance->num=20;
         8     }
         9     return  mSingletonInstance;
        10 }
        11 void SingletonLazyMode::InstanceDispose()
        12 {
        13     if(mSingletonInstance!=NULL)
        14     {
        15         delete mSingletonInstance;
        16         mSingletonInstance=NULL;
        17     }
        18 }
        19 void SingletonLazyMode::sayHi()
        20 {
        21     qDebug()<<"hi lazy man! Number:"<<num;
        22 }
        23 SingletonLazyMode::SingletonLazyMode()
        24 {
        25 }
  2. 饿汉模式
    • 先看头文件.h代码
      • 1 class SingletonEagerMode
        2 {
        3 public:
        4     static SingletonEagerMode* GetInstance();
        5     static void InstanceDispose();
        6 private:
        7     SingletonEagerMode();
        8     static SingletonEagerMode* mEagerInstance;
        9 };
    • .cpp实现
      •  1 SingletonEagerMode *SingletonEagerMode::mEagerInstance=new SingletonEagerMode();
         2 SingletonEagerMode *SingletonEagerMode::GetInstance()
         3 {
         4     return  mEagerInstance;
         5 }
         6 void SingletonEagerMode::InstanceDispose()
         7 {
         8     if(mEagerInstance!=NULL)
         9     {
        10         delete mEagerInstance;
        11         mEagerInstance=NULL;
        12     }
        13 }
        14 SingletonEagerMode::SingletonEagerMode()
        15 {
        16
        17 }
  • 测试代码
    •  1 SingletonLazyMode* lazyinstance1=SingletonLazyMode::GetInstance();//lazy mode 懒汉模式
       2     SingletonLazyMode* lazyinstance2=SingletonLazyMode::GetInstance();
       3     lazyinstance1->sayHi();
       4     lazyinstance2->sayHi();
       5     qDebug()<<"lazyinstance1 address:"<<lazyinstance1;
       6     qDebug()<<"lazyinstance2 address:"<<lazyinstance2;
       7     SingletonLazyMode::InstanceDispose();
       8     qDebug()<<"lazyinstance1 address:"<<lazyinstance1;
       9     qDebug()<<"lazyinstance2 address:"<<lazyinstance2;
      10     lazyinstance1->sayHi();
      11     lazyinstance2->sayHi();
      12     SingletonEagerMode* eagerinstance1=SingletonEagerMode::GetInstance();//eager mode 饿汉模式
      13     SingletonEagerMode* eagerinstance2=SingletonEagerMode::GetInstance();
      14     qDebug()<<"eagerinstance1 address:"<<eagerinstance1;
      15     qDebug()<<"eagerinstance2 address:"<<eagerinstance2;
  • 运行效果:
    • 这里咱们能够看到不论是懒汉仍是饿汉模式两个实例的地址均是相同的。说明咱们的单例是OK的
  • 技术总结: 
  • 下面对懒汉模式和饿汉模式经行对比分析其异同点:
    • 相同点:
      • 懒汉/饿汉模式实现结构基本相似
    • 不一样点:
      • 懒汉模式初始化对象是在程序调用的时候,非线程安全,因为最终实现要加Qmutex进行枷锁处理,执行效率会相对而言要低
      • 饿汉模式是程序启动的时候就已经建立好了,浪费内存,但属于线程安全,执行效率相对懒汉而言要高
  •  问题点:
  • 细心的同窗可能发现了,在上面测试过程当中我调用了本身定义的Dispose接口,可是仍是能再次调用lazyinstance1,lazyinstance2实例中的函数和变量???这是在MinGW编译器下执行的结果,当我将编译器换成MSVC时,显示内存已经被释放掉了
  • 下图MSVC下的执行结果
  •  参考了一篇博客也没看出什么问题:https://www.cnblogs.com/chengjundu/p/11283123.html设计模式

  • 若是有朋友知道望不吝赐教!!!!感谢。安全

  • 最后看看咱们线程安全的懒汉实现方式函数

  •  .h文件性能

     1 class SingletonThreadSafety
     2 {
     3 public:
     4     static SingletonThreadSafety* GetInstance();
     5     static void InstanceDispose();
     6 private:
     7     SingletonThreadSafety();
     8     static SingletonThreadSafety* mSaftyInstance;
     9     static QMutex mMutex;
    10 };
  • .cpp代码
  •  1 SingletonThreadSafety *SingletonThreadSafety::mSaftyInstance=NULL;
     2 QMutex SingletonThreadSafety::mMutex;
     3 SingletonThreadSafety *SingletonThreadSafety::GetInstance()
     4 {
     5     if(mSaftyInstance==NULL)
     6     {
     7         QMutexLocker locker(&mMutex);
     8         if(mSaftyInstance==NULL)
     9         {
    10             mSaftyInstance=new SingletonThreadSafety();
    11         }
    12     }
    13     return  mSaftyInstance;
    14 }
    15 void SingletonThreadSafety::InstanceDispose()
    16 {
    17     if(mSaftyInstance!=NULL)
    18     {
    19         delete mSaftyInstance;
    20         mSaftyInstance=NULL;
    21     }
    22 }
    23 SingletonThreadSafety::SingletonThreadSafety()
    24 {
    25 
    26 }

    这就完美解决掉了线程安全问题,可是在获取实例对象的时候须要对Qmutex进行判断,这会损失一点点性能。测试

  • 以上就是对单例模式的完整概述
  • 下面进行全面的技术总结:
  • 在写单例模式的时候咱们要考虑到如下几个方面:
  1. 要封闭默认的构造函数,以防止多地方建立对象
  2. 类提供一个静态的对象,用来保存该实例
  3. 提供一个公共的访问实例的接口GetInstance
  4. 考虑线程安全问题

以上单例全部内容,若有错误请指出!!!this

参考<<大话设计模式>>一书spa

附源代码:线程

https://gitee.com/xiaochunlu/designer-pattern/tree/master/Singleton_Pattern