序言
今天開始學習23種設計模式的第一個模式——單例模式,以前在網上也看過不少人寫的單例模式博文,也算已經吸收了不少。今天看《設計模式之禪》里對設計模式的講解,作者說的還挺細致的。唯一可惜的是作者用java講解單例模式,而我自己從去年工作后就用的是C++,本着學什么用什么的原則,我就用C++來記錄一下學習成果吧。希望這篇博客能把我學到的分享給跟我一樣在園子的讀者。
正文
1. 單例模式的現實場景
作者開篇用皇帝來類比單例,而我有個更好的例子,且聽我一一道來。太陽系中有一個恆星和九大行星,至少我上學的時候是九大行星,后來因為冥王星太小了,被除名了。可是我還是覺得九這個數字比較好,比如九大行星,九星連珠,說起來爽口。扯遠了,單例模式在這太陽系中相當於什么呢?太陽。是的,太陽。地球說,我要陽光;金星說,我要陽光。這些陽光都來自同一個太陽,也就是說關於太陽這個類的對象,整個太陽系只有一個。這就是單例模式,無論我在太陽系的哪個行星上,我得到的陽光都是來自同一個太陽的。
2. 單例模式實現(一)——非同步單例模式、懶漢式單例
1 #include <iostream> 2 using namespace std; 3 4 class Singleton{ 5 public: 6 static Singleton* getInstance(){ 7 if(instance == NULL){ 8 instance = new Singleton(); 9 } 10 return instance; 11 } 12 private: 13 static Singleton *instance; 14 Singleton(){}; // 使得該類不能在外部初始化 15 }; 16 Singleton* Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義 17 18 int main(){ 19 Singleton *instance = Singleton::getInstance(); 20 system("pause"); 21 return 0; 22 }
源碼實現解讀
上面的代碼是完整的控制台程序,VS2010 Pro下可以正確編譯運行。下面對這段程序的重點進行介紹一下:
■ 類Singleton擁有一個靜態成員指針變量,該指針為整個類共享
■ 類Singleton擁有一個私有的無參構造函數,這說明,在類外是無法通過構造方法來實例化對象的
■ 類Singleton擁有一個getInstance的方法,這個方法內部用於實例化instance成員指針變量
上面的單例實現又被稱為懶漢式的單例模式,為什么呢?因為類Singleton的成員instance只有在被調用的時候才會被初始化,所以被稱為懶漢式單例實現。
問題缺點
上面的實現有沒有問題呢?有。在多線程場景中,上面的代碼很容易產生多個實例,為什么呢?我畫了一張圖,如下:

從上圖可以看出,在多線程環境下,如果還是按照上面的代碼實現的話,就會發生多次創建instance的情況,所以上面的代碼還是有問題的,不過僅僅是在多線程的環境下。
3. 單例模式實現(二)——同步單例模式、餓漢式單例
針對2中出現的多線程問題,我們用下面的代碼就可以很輕易的結局。
1 #include <iostream> 2 using namespace std; 3 4 class Singleton{ 5 public: 6 static Singleton getInstance(){ 7 return instance; 8 } 9 private: 10 static Singleton instance; 11 Singleton(){}; // 使得該類不能在外部初始化 12 }; 13 Singleton Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義 14 15 int main(){ 16 Singleton instance = Singleton::getInstance(); 17 system("pause"); 18 return 0; 19 }
上面的代碼在類創建的時候,實例instance就被創建了,這樣就不會發生多線程多次創建實例的情況了。程序解讀如下:
■ 類Singleton擁有一個靜態實例變量,而不是指針
■ 類Singleton擁有一個私有構造方法,這跟懶漢式是一樣的。
上面的實現為什么叫惡漢式單例呢?因為上面的實現在調用Singleton的getInstance之前就應景實例化了一個靜態的屬於該類的靜態成員變量。
問題缺點
上面的實現結局了多線程問題,那還有沒有問題呢?有。仔細觀察一下,發現有這樣一句話:Singleton instance = Singleton::getInstance(); 創建的對象通過Singleton的賦值運算符被賦值給了instance了,這就不好了,雖然對類Singleton中的instance來說,永遠只有一個。但是它被復制了,這樣就不是單例了,那怎么辦呢?辦法很簡單,就是把類Singleton的拷貝構造函數和賦值操作符變為private就可以了,修改后的代碼如下:
1 #include <iostream> 2 using namespace std; 3 4 class Singleton{ 5 public: 6 static Singleton getInstance(){ 7 return instance; 8 } 9 private: 10 static Singleton instance; 11 Singleton(){}; // 使得該類不能在外部初始化 12 Singleton(const Singleton& singleton); 13 Singleton & operator = (const Singleton&); 14 }; 15 Singleton Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義 16 17 int main(){ 18 //Singleton instance = Singleton::getInstance(); // 無法通過編譯 19 system("pause"); 20 return 0; 21 }
4. 單例模式中對象的銷毀
對於懶漢式的單例實現,我們在getInstance函數里看到了new字樣,那是肯定需要用delete銷毀的,否則會出現內存溢出的,那要怎樣用代碼來實現呢?追加一個析構函數就可以了,參考如下代碼:
1 class Singleton{ 2 public: 3 static Singleton* getInstance(){ 4 if(instance == NULL){ 5 instance = new Singleton(); 6 } 7 return instance; 8 } 9 ~Singleton(){ 10 if(instance != NULL){ 11 delete instance; 12 } 13 } 14 private: 15 static Singleton* instance; 16 Singleton(){}; // 使得該類不能在外部初始化 17 }; 18 Singleton* Singleton::instance; // 注意,類里面的instance只是聲明,static變量必須類外定義
其他
在java中,解決多線程的問題可以有很多種,本文就不討論了,有興趣的可以參考:http://blog.csdn.net/it_man/article/details/5787567,這篇博客寫的代碼還是挺翔實的。
總結
在不考慮多線程的情況下,可以使用懶漢式單例實現方法。而在有多線程的環境下,最好用餓漢式單例實現方法。同時要注意的是,要把構造函數、默認構造函數、賦值運算符重載都改為private的。
PS:有錯誤的地方還請指出。謝謝。
