淺談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

相關文章
相關標籤/搜索