軟件開發經常使用設計模式—單例模式總結(c++版)

單例模式:就是隻有一個實例。c++

singleton pattern單例模式:確保某一個類在程序運行中只能生成一個實例,並提供一個訪問它的全局訪問點。這個類稱爲單例類。如一個工程中,數據庫訪問對象只有一個,電腦的鼠標只能鏈接一個,操做系統只能有一個窗口管理器等,這時能夠考慮使用單例模式。數據庫

衆所周知,c++中,類對象被建立時,編譯系統爲對象分配內存空間,並自動調用構造函數,由構造函數完成成員的初始化工做,也就是說使用構造函數來初始化對象。安全

一、那麼咱們須要把構造函數設置爲私有的 private,這樣能夠禁止別人使用構造函數建立其餘的實例。多線程

二、又單例類要一直向系統提供這個實例,那麼,須要聲明它爲靜態的實例成員,在須要的時候,才建立該實例。函數

三、且應該把這個靜態成員設置爲 null,在一個public 的方法裏去判斷,只有在靜態實例成員爲 null,也就是沒有被初始化的時候,纔去初始化它,且只被初始化一次。性能

一般咱們可讓一個全局變量使得一個對象被訪問,但它不能阻止你實例化多個對象。若是採用全局或者靜態變量的方式,會影響封裝性,難以保證別的代碼不會對全局變量形成影響。測試

一個最好的辦法是,讓類自身負責保存它的惟一實例。這個類能夠保證沒有其餘實例能夠被建立,而且它能夠提供一個訪問該實例的方法,單例模式比全局對象好還包括,單例類能夠繼承。spa

單例模式又分爲兩種基本的情形:餓漢式和懶漢式操作系統

直接在靜態區初始化 instance,而後經過 get 方法返回,這樣這個類每次直接先生成一個對象,好像很久沒吃飯的餓漢子,急着吃飯同樣,急切的 new 對象,這叫作餓漢式單例類。或者是在 get 方法中才 new instance,而後返回這個對象,和懶漢字同樣,不主動作事,須要調用 get 方法的時候,才 new 對象,這就叫作懶漢式單例類。線程

 

以下是懶漢式單例類

 1 //單例模式示例
 2 class Singleton
 3 {
 4 public:
 5     static Singleton * getInstance()
 6     {
 7         if (instance == NULL) {
 8             instance = new Singleton();
 9         }
10         
11         return instance;
12     }
13     
14 private:
15     //私有的構造函數,防止外人私自調用
16     Singleton()
17     {
18         cout << "實例化了" << count << "個對象!" << endl;
19         count++;
20     }
21     //聲明一個靜態實例,靜態函數只能使用靜態的數據成員。整個類中靜態成員只有一個實例,一般在實現源文件中被初始化。
22     static Singleton *instance;
23     //記錄實例化的對象
24     int count = 1;
25 };
26 
27 Singleton * Singleton::instance = NULL;
28 
29 int main(void)
30 {
31     Singleton::getInstance();
32     Singleton::getInstance();
33     Singleton::getInstance();
34     Singleton::getInstance();
35     
36     return 0;
37 }

實例化了1個對象!

Program ended with exit code: 0

小結:

懶漢式單例模式是用時間換取控件,餓漢式單例模式,是用空間換取時間。

 

繼續分析,考慮多線程下的懶漢式單例模式

上述代碼在單線程的狀況下,運行正常,可是遇到了多線程就出問題,假設有兩個線程同時運行了這個單例類,同時運行到了判斷 if 語句,而且當時,instance 實例確實沒有被初始化呢,那麼兩個線程都會去運行並建立實例,此時就不知足單例類的要求了。那麼咱們須要寫上線程同步的功能。

 1 //考慮到多線程情形下的單例模式
 2 class Singleton
 3 {
 4 public:
 5     //get 方法
 6     static Singleton * getInstance(){
 7         //聯繫互斥信號量機制,給代碼加鎖
 8         lock();
 9         //判斷 null
10         if (NULL == instance) {
11             //判斷類沒有生成對象,才實例化對象,不然再也不實例化
12             instance = new Singleton();
13         }
14         //使用完畢,解鎖
15         unlock();
16         //返回一個實例化的對象
17         return instance;
18     }
19 private:
20     //聲明對象計數器
21     int count = 0;
22     //聲明一個靜態的實例
23     static Singleton *instance;
24     //私有構造函數
25     Singleton(){
26         count++;
27         cout << "實例化了" << count << "個對象!" << endl;
28     }
29 };
30 //初始化 instance
31 Singleton * Singleton::instance = NULL;

此時,仍是有 ab 兩個線程來運行這個單例類,因爲在同一時刻,只有一個線程能拿到同步鎖(互斥信號量機制),a 拿到了同步鎖,b 只能等待,若是 a發現實例還沒建立,a 就會建立一個實例,建立完畢,a 釋放同步鎖,而後 b 才能拿到同步鎖,繼續運行接下來的代碼,b 發現 a 線程運行的時候,已經生成了一個實例,b 線程就不會重複建立實例了,這樣就保證了咱們在多線程環境中只能獲得一個實例。

 

繼續分析多線程下的懶漢式單例模式

代碼中,每次 get 方法中,獲得 instance,都要判斷是否爲空,且判斷是否爲空以前,都要先加同步鎖,若是線程不少的時候,就要先等待加了同步鎖的線程運行完畢,才能繼續判斷餘下的線程,這樣就會形成大量線程的阻塞,且加鎖是個很是消耗時間的過程,應該儘可能避免(除非頗有必要的時候)。可行的辦法是,雙重判斷方法。

由於,只是在實例尚未建立的時候,須要加鎖判斷,保證每次只有一個線程建立實例,而當實例已經建立以後,其實就不須要加鎖操做了。

 

雙重判斷的線程安全的懶漢式單例模式 

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //先判斷一次 null,只有 null 的時候須要加鎖,其餘的時候,其實不須要加鎖
 7         if (NULL == instance) {
 8             //聯繫互斥信號量機制,給代碼加鎖
 9             lock();
10             //而後再次判斷 null
11             if (NULL == instance) {
12                 //判斷類沒有生成對象,才實例化對象,不然再也不實例化
13                 instance = new Singleton();
14             }
15             //使用完畢,解鎖
16             unlock();
17         }
18                 //返回一個實例化的對象
19         return instance;
20     }
21 private:
22     //聲明對象計數器
23     int count = 0;
24     //聲明一個靜態的實例
25     static Singleton *instance;
26     //私有構造函數
27     Singleton(){
28         count++;
29         cout << "實例化了" << count << "個對象!" << endl;
30     }
31 };
32 //初始化 instance
33 Singleton * Singleton::instance = NULL;

這樣的雙重檢測機制,提升了單例模式在多線程下的效率,由於這樣的代碼,只須要在第一次建立實例的時候,須要加鎖,其餘的時候,線程無需排隊等待加鎖以後,再去判斷了,比較高效。

 

再看餓漢式的單例模式,以前看了懶漢式的單例類,是線程不安全的,經過加鎖(雙重鎖),實現線程安全

回憶餓漢式單例類:直接在靜態區初始化 instance,而後經過 get 方法返回,這樣這個類每次直接先生成一個對象,好像很久沒吃飯的餓漢子,急着吃飯同樣,急切的 new 對象,這叫作餓漢式單例類。

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //返回一個實例化的對象
 7         return instance;
 8     }
 9 private:
10     //聲明一個靜態的實例
11     static Singleton *instance;
12     //私有構造函數
13     Singleton(){
14     
15     }
16 };
17 //每次先直接實例化instance,get 方法直接返回這個實例
18 Singleton * Singleton::instance = new Singleton();

注意:靜態初始化實例能夠保證線程安全,由於靜態實例初始化在程序開始時進入主函數以前,就由主線程以單線程方式完成了初始化!餓漢式的單例類,也就是靜態初始化實例保證其線程安全性,故在性能需求較高時,應使用這種模式,避免頻繁的鎖爭奪。

 

繼續看單例模式

上面的單例模式沒有 destory() 方法,也就是說,貌似上面的單例類沒有主動析構這個惟一實例!然而這就致使了一個問題,在程序結束以後,該單例對象沒有delete,致使內存泄露!下面是一些大神的方法:一個妥善的方法是讓這個類本身知道在合適的時候把本身刪除,或者說把刪除本身的操做掛在操做系統中的某個合適的點上,使其在恰當的時候被自動執行。

咱們知道,程序在結束的時候,系統會自動析構全部的全局變量。事實上,系統也會析構全部的類的靜態成員變量,就像這些靜態成員也是全局變量同樣。若是在類的析構行爲中有必須的操做,好比關閉文件,釋放外部資源,那麼上面的代碼沒法實現這個要求。咱們須要一種方法,正常的刪除該實例。利用這些特徵,咱們能夠在單例類中定義一個這樣的靜態成員變量,而它的惟一工做就是在析構函數中刪除單例類的實例。以下面的代碼中的Garbage類:

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //判斷單例否
 7         if (NULL == instance) {
 8             instance = new Singleton();
 9         }
10         //返回一個實例化的對象
11         return instance;
12     }
13     //c++ 嵌套的內部類,做用是刪除單例類對象,Garbage被定義爲Singleton的內嵌類,以防該類被在其餘地方濫用。
14     class Garbage
15     {
16     public:
17         ~Garbage(){
18             if (Singleton::instance != NULL) {
19                 cout << "單例類的惟一實例被析構了" << endl;
20                 delete Singleton::instance;
21             }
22         }
23     };
24     
25 private:
26     //單例類中聲明一個觸發垃圾回收類的靜態成員變量,它的惟一工做就是在析構函數中刪除單例類的實例,利用程序在結束時析構全局變量的特性,選擇最終的釋放時機;
27     static Garbage garbage;
28     //聲明一個靜態的實例
29     static Singleton *instance;
30     //單例類的私有構造函數
31     Singleton(){
32         cout << "調用了單例類的構造函數" << endl;
33     }
34     //單例類的私有析構函數
35     ~Singleton(){
36         cout << "調用了單例類的析構函數" << endl;
37     }
38 };
39 //初始化內部的靜態變量,目擊是啓動刪除的析構函數,若是不初始化,就不會被析構
40 //內部類能夠訪問外部類的私有成員,外部類不能訪問內部類的私有成員!
41 Singleton::Garbage Singleton::garbage;
42 //初始化instance爲 null
43 Singleton * Singleton::instance = NULL;
44 
45 int main(void)
46 {
47     Singleton *a = Singleton::getInstance();
48     Singleton *b = Singleton::getInstance();
49     Singleton *c = Singleton::getInstance();
50     
51     if (a == b) {
52         cout << "a = b" << endl;
53     }
54     
55     return 0;
56 }

調用了單例類的構造函數

a = b

單例類的惟一實例被析構了

調用了單例類的析構函數

Program ended with exit code: 0

 

類Garbage被定義爲Singleton的內嵌類,以防該類在其餘地方濫用,程序運行結束時,系統會調用Singleton的靜態成員garbage的析構函數,該析構函數會刪除單例的惟一實例,使用這種方法釋放單例對象有如下特徵:

一、在單例類內部定義專有的嵌套類;

二、在單例類內定義私有的專門用於釋放的靜態成員;

三、利用程序在結束時析構全局變量的特性,選擇最終的釋放時機;

四、使用單例的代碼不須要任何操做,沒必要關心對象的釋放。

 

其實,繼續想單例類的實現,有的人會這樣作:

在程序結束時調一個專門的方法,這個方法裏判斷實例對象是否爲 null,若是不爲 null,就對返回的指針掉用delete操做。這樣作能夠實現刪除單例的功能,但不只很醜陋,並且容易出錯。由於這樣的附加代碼很容易被忘記,並且也很難保證在delete以後,沒有代碼再調用GetInstance函數。不推薦直接的刪除方法。

 

繼續查看單例模式:單例模式在實際開發過程當中是頗有用的,單例模式的特徵總結:

一、一個類只有一個實例

二、提供一個全局訪問點

三、禁止拷貝

逐個分析:

一、實現只有一個實例,須要作的事情:將構造函數聲明爲私有

二、提供一個全局訪問點,須要作的事情:類中建立靜態成員和靜態成員方法

三、禁止拷貝:把拷貝構造函數聲明爲私有,而且不提供實現,將賦值運算符聲明爲私有,防止對象的賦值

完整的單例類實現代碼以下:

class Singleton
{
public:
    //get 方法
    static Singleton * getInstance(){
        if (NULL == instance) {
            lock();
            //判斷單例否
            if (NULL == instance) {
                instance = new Singleton();
            }
            unlock();
        }
        //返回一個實例化的對象
        return instance;
    }
    //c++ 嵌套的內部類,做用是刪除單例類對象,Garbage被定義爲Singleton的私有內嵌類,以防該類被在其餘地方濫用。
    class Garbage
    {
    public:
        ~Garbage(){
            if (Singleton::instance != NULL) {
                cout << "單例類的惟一實例被析構了" << endl;
                delete Singleton::instance;
            }
        }
    };
    
private:
    //單例類中定義一個這樣的靜態成員變量,而它的惟一工做就是在析構函數中刪除單例類的實例,利用程序在結束時析構全局變量的特性,選擇最終的釋放時機;
    static Garbage garbage;
    //聲明一個靜態的實例
    static Singleton *instance;
    //單例類的私有構造函數
    Singleton(){
        cout << "調用了單例類的構造函數" << endl;
    }
    //單例類的私有析構函數
    ~Singleton(){
        cout << "調用了單例類的析構函數" << endl;
    }
    //把拷貝構造函數聲明爲私有,就能夠禁止外人拷貝對象,也不用實現它,聲明私有便可
    Singleton(const Singleton &copy);
    //把賦值運算符重載爲私有的,防止對象之間的賦值操做
    Singleton & operator=(const Singleton &other);
};
//初始化內部似有淚的靜態變量,目擊是啓動刪除的析構函數,若是不初始化,就不會被析構
//內部類能夠訪問外部類的私有成員,外部類不能訪問內部類的私有成員!
Singleton::Garbage Singleton::garbage;
//初始化instance爲 null
Singleton * Singleton::instance = NULL;

int main(void)
{
    Singleton *a = Singleton::getInstance();
    Singleton *b = Singleton::getInstance();
    Singleton *c = Singleton::getInstance();
    
    if (a == b) {
        cout << "a = b" << endl;
    }
    
    return 0;
}

單例類de測試,兩個方法:

一、實例化多個對象,看調用了幾回構造函數,若是隻調用一次,說明只建立一個實例

二、單步跟蹤,查看對象的地址,是否同樣,同樣則爲一個對象

相關文章
相關標籤/搜索