| 如果某個類管理了系統(tǒng)中唯一的某種資源,那么我們只能創(chuàng)建該類的一個實例,此時用到singleton設(shè)計模式(后面為了簡化將省略“設(shè)計模式”四個字)就比較合適了。然而,如果不注意實現(xiàn)方法,就很有可能會讓我們碰到一些莫名其妙的錯誤。圖1是經(jīng)過簡化所得到的一個實現(xiàn)錯誤的例子。 main.c 00001: #include <iostream> 00002: 00003: using namespace std; 00004: 00005: class singleton1_t 00006: { 00007: public: 00008:     static singleton1_t *instance () 00009:     { 00010:         return &instance_; 00011:     } 00012: 00013:     void count_increase () {count_ ++;} 00014:     int count () const {return count_;} 00015: 00016: private: 00017:     singleton1_t (): count_ (0) {} 00018:     ~singleton1_t () {} 00019: 00020:     static singleton1_t instance_; 00021:     int count_; 00022: }; 00023: 00024: class singleton2_t 00025: { 00026: public: 00027:     static singleton2_t *instance () 00028:     { 00029:         return &instance_; 00030:     } 00031: 00032: private: 00033:     singleton2_t () {singleton1_t::instance ()->count_increase ();} 00034:     ~singleton2_t () {} 00035: 00036:     static singleton2_t instance_; 00037: }; 00038: 00039: singleton2_t singleton2_t::instance_; 00040: singleton1_t singleton1_t::instance_; 00041: 00042: int main () 00043: { 00044:     (void) singleton2_t::instance (); 00045:     cout << "count = " << singleton1_t::instance ()->count () << endl; 00046:     return 0; 00047: } 
 圖1  圖中的兩個類在實現(xiàn)singleton時都將類的構(gòu)造和析構(gòu)函數(shù)的對外可視性設(shè)為private,這是實現(xiàn)singleton首先要注意的一個點。通過這一手段,有助于預(yù)防他人粗心地定義類實例。 圖中的singleton2_t類在其構(gòu)造函數(shù)中調(diào)用singleton1_t類的count_increase ()方法使計數(shù)加一。第44行的代碼用于代表使用singleton2_t實例。第46行代碼則顯示singleton1_t類的記數(shù)信息。圖2示例了該程序的運行結(jié)果。
 $ g++ main.cpp -o singleton.exe $ ./singleton.exe count = 0 
 圖2 
 是不是對于最終的顯示計數(shù)為0而不是1感到奇怪?錯誤發(fā)生的原因在于,singleton2_t類實例的構(gòu)造是先于singleton1_t類的,當singleton1_t類的實例在最后構(gòu)造時會把count_變量置成0,從而覆蓋singleton2_t的構(gòu)造函數(shù)所引起的變更。 盡管這是一個精心設(shè)計的錯誤,但在大型項目中出現(xiàn)這類錯誤的可能性卻并不小。因為在現(xiàn)實項目中,singleton1_t和singleton2_t兩個類的實現(xiàn)很可能是在不同的源文件中,這勢必造成兩個類實例的初始化順序會因鏈接順序不同而不同,《揭示C++中全局類變量的構(gòu)造與析構(gòu)順序》一文介紹了這是為什么。
在本例中,如果將第39行和第40行的代碼進行對調(diào)就不會出現(xiàn)這種奇怪的現(xiàn)象,但這不是解決問題的終極方法。更好的方法需要更改singleton的實現(xiàn)方法,圖3示例了一種新的實現(xiàn)方法。
 main.c 00001: #include <iostream> 00002: 00003: using namespace std; 00004: 00005: class singleton1_t 00006: { 00007: public: 00008:     static singleton1_t *instance () 00009:     { 00010:         if (0 == p_instance_) { 00011:             p_instance_ = new singleton1_t; 00012:         } 00013:         return p_instance_; 00014:     } 00015: 00016:     void count_increase () {count_ ++;} 00017:     int count () const {return count_;} 00018: 00019: private: 00020:     singleton1_t (): count_ (0) {} 00021:     ~singleton1_t () {} 00022: 00023:     static singleton1_t *p_instance_; 00024:     int count_; 00025: }; 00026: 00027: class singleton2_t 00028: { 00029: public: 00030:     static singleton2_t *instance () 00031:     { 00032:         if (0 == p_instance_) { 00033:             p_instance_ = new singleton2_t; 00034:         } 00035:         return p_instance_; 00036:     } 00037: 00038: private: 00039:     singleton2_t () {singleton1_t::instance ()->count_increase ();} 00040:     ~singleton2_t () {} 00041: 00042:     static singleton2_t *p_instance_; 00043: }; 00044: 00045: singleton2_t *singleton2_t::p_instance_ = 0; 00046: singleton1_t *singleton1_t::p_instance_ = 0; 00047: 00048: int main () 00049: { 00050:     singleton2_t::instance (); 00051:     cout << "count = " << singleton1_t::instance ()->count () << endl; 00052:     return 0; 00053: } 
 圖3  
 新實現(xiàn)最大的變化,在于將以前的類靜態(tài)變量從類實例變成了類指針,并在instance()函數(shù)中需要時通過new操作符創(chuàng)建類實例。指針在C++中仍是當作一種原始數(shù)據(jù)類型處理的,其初始化與類實例的初始化不同,不需調(diào)用類構(gòu)造函數(shù)。在這一實現(xiàn)中,兩個類的靜態(tài)變量p_instance_的初始化都是在程序的.bss段初始化時一次性完成的。   這一實現(xiàn)中由于類的實例是通過new操作符獲得的,所以需要為類定義釋放實例的函數(shù)(圖中省略了),并由在合適的時機調(diào)用。為了省去這類麻煩,作者更推崇圖4所示的實現(xiàn)方式。 main.c 00005: class singleton1_t 00006: { 00007: public: 00008:     static singleton1_t *instance () 00009:     { 00010:         if (0 == p_instance_) { 00011:             static singleton1_t instance; 00012:             p_instance_ = &instance; 00013:         } 00014:         return p_instance_; 00015:     } 00016: 00017:     void count_increase () {count_ ++;} 00018:     int count () const {return count_;} 00019: 00020: private: 00021:     singleton1_t (): count_ (0) {} 00022:     ~singleton1_t () {} 00023: 00024:     static singleton1_t *p_instance_; 00025:     int count_; 00026: }; 
 圖4  通過在函數(shù)內(nèi)部定義靜態(tài)變量的方法獲得類實例,一方面簡化了類接口的實現(xiàn),另一方面又降低了因為忘記調(diào)用釋放接口函數(shù)而導(dǎo)致內(nèi)存泄漏的可能。需要提醒的是,在這種實現(xiàn)方法中,類實例的構(gòu)造是發(fā)生在各類的instance()函數(shù)第一次被調(diào)用時,而各實例的析構(gòu)又是以與構(gòu)造相反的順序進行的,且后者是由編程語言環(huán)境所保證的。 |