[推薦] Singleton 模式在C++中的一種實現 |
|
axsoft
版主 發表:681 回覆:1056 積分:969 註冊:2002-03-13 發送簡訊給我 |
Singleton 模式在C 中的一種實現說明:本文介紹了Singleton模式在C 中的一種實現方式,此方式不但較好的解決了全部Singleton對象析構的次序問題,也提出了一個解決Singleton派生類體系的方法。 by: 方泓 資料來源:http://www.cpp3d.com/articles/show.asp?aid=103 介紹 在我們的編程實踐中經常會遇到這樣的情況,象Logger, MemoryManager,SystemInformation等這些類在整個系統中只應存在一個實例,這時我們就可以使用Singleton模式來實現這些類。Singleton是最基本最常使用的設計模式之一,很多模式可以使用Singleton模式實現,如Abstract Factory, Builder, Prototype往往是通過Singleton來實現的。 在經典的GoF書[1]中它的Intent是這樣定義的:Ensure a class only has one instance, and provide a global point of access to it. (保証一個類僅有一個實例,並提供一個訪問它的全局訪問點。) Singleton不僅引入自然,使用簡捷方便,而且也是解決C 中non-local static objects無法以某種正確次序初始化的一個極好方案。這在GoF[1]書中有所介紹,在Scott Meyers 的 Effective C [2] 書中的Item 47,"Ensure that non-local static objects are initialized before they're used. " 中有關于此點更為詳細的說明。若非如此,可能需要一些不夠優美的方法來解決這個問題,在MFC的實現源碼中我們可以看到有許多#pragma init_seg(lib), 就能部分解決全局變量初始化的次序問題,但這種解決不但難看,而且不是C 標准的一部分,因而是不可移植的。 實現 不奇怪,Singleton在C 中也有無數種實現方法,也各有不同的優缺點和適用範圍,有些可能很簡單,也有些會很複雜,不過實現的基礎一般都是採用local static object方法來實現,好處在Effective C Item 47中有較詳細的說明[2]。 在這篇中我將介紹一種我現在一直在使用的一種實現。 由于Singleton的類之間的可能存在依賴關系,所以在很多時候析構的次序也很重要。Evgeniy Gabrilovich 在C Report上有一篇文章[3]較好的解決了這個問題,Evgeniy Gabrilovich引入了一個Singleton的析構管理類,每個singleton的類在注冊到這個析構管理器時都帶有一個phase值,phase值越小的Singleton實例,則應該越晚被析構。在析構各個注冊的Singleton類實例時先根據Singleton類的phase值排序后再進行析構操作,這樣只要各Singleton中不出現循環的依賴關系,這種方法就是可行的,更詳細的介紹可見[3]。 我對此方法做一些擴展,下面我結合實現代碼來進行說明。template在此定義里我們可以看到TSingleton的constructor和destructor是protected的,這是因為我們不想讓TSingleton單獨使用,而是讓它做為Singleton類的基類來使用。 T::sc_nClassPhase為Singleton類的一個靜態常量,這有點象類的ID,但並不和ID完全一樣,后面還會有更詳細的說明。 這個類很簡單,可能你唯一奇怪的地方就是TDestructor和CDestructionManager了。我們接著看看它們的實現: // -- forward declarations -- class CDestructor; template這里CDestructionManager類就是我們前面提到過的Singleton實例析構的管理類,這里我們可以看到一個有趣的現象,CDestructionManager也是一個從TSingleton派生出來的對象,很容易理解,因為它只應該有唯一實例,所以應該是一個Singleton類。它需要自己管理自己,要注意它析構時不能漏了自己,而且它應該最后被析構,所以我給它分配了系統最小的phase值,只要保証最小就行,在這里我定為此phase值整數1。 可以看到在CDestructionManager類中維護一個CDestructor指針數組,用于系統每一個注冊的Singleton類實例的析構。CDestructor中有一個虛函數Destroy()就是起這個作用的,它是一個純虛函數,在CDestructor的派生類TDestructor中來正確實現析構代碼。看TDestructor的實現就知道它是一個非常簡單的template類。 代碼中的SDestroyObjects的唯一作用就是在系統結束時自動調用DestroyObjects(),從DestroyObjects()的實現可以看出它先根據Singleton類的phase值進行排序,然后對數組中每一個元素調用Destroy()。SDestroyObjects和DestroyObjects()都設成private的作用是阻止編程者的自己調用。 由于CDestructionManager本身也是一個Singleton類,所以我們還可以從中掌握如何使用TSingleton類,需要注意的是兩個宏M_DefineSingleton和M_RegisterTypeOfSingletonPhase。 #define M_DefineSingleton(mClass, mPhase) \ M_Noncopyable(mClass)\ public:\ BOOST_STATIC_CONSTANT(int, sc_nClassPhase = mPhase);\ typedef TSingleton用了macro總會使代碼更難懂一些,但比每次使用鍵入一些相同的東西要好一些。在這里我說明一下,由Singleton類的特點所知不應該有copy constructor 和 assignment operator。M_Noncopyable就是起這個作用的。 BOOST_STATIC_CONSTANT定義了我在前面說明過的Singleton類的靜態常量phase值, 定義了friend class TDestructor 和 TSingleton是因為Singleton類的定義中往往會將構造函數定義成非public的。 using baseclass::Instance這句是因為我們使用了private繼承,所以Instance()在派生類(我們要實現的Singleton類)中也是private的,在使用MySingleton::Instance()這樣的語句時就會產編譯錯誤,這時使用using這句話可以用來解決這個問題。 剩下來唯一的疑問可能就是M_TypeOfID了,它也是一個宏,我們來看看它的實現: namespace meta { template這里利用了template specialization技術來實現整數和一個類的一一對應關系,TType2Type具體方法可見 Andrei Alexandrescu 發表在CUJ上的文章[4]。 這段代碼的目的有兩個,一是防止同一個ID對應兩個Type,另一個是只要在編譯時期知道ID,就可以得到對應的Type,這兩條都很重要,第一條可以阻止一些錯誤的發生,如果兩個Singleton類使用同一個phase值,在編譯M_RegisterTypeOfSingletonPhase時就會報錯,第二個的體現就是我們已經在使用的宏M_TypeOfID。 再回頭看看TSingleton類的Instance()的實現, static T* s_pInstance = new M_TypeOfSingletonPhase(T); 這里可能和其他人的寫法不太一樣,我並沒有寫成: static T* s_pInstance = new T; 這是我將phase值移到類的靜態常量后的一個好處,可以用此方法來實現派生類體系的Singleton,要點是在派生類中不要重新定義類phase值,而在利用M_RegisterTypeOfSingletonPhase時,只注冊派生類。 關于本實現的一個例程可見Listing 1,這個例程的運行結果可見Listing 2。可以看出phase值對析構次序所起的作用。CLogger的實例在CResource的實例析構后才析構,這是因為我們定義了 CLogger的phase值為500, 而CResource的phase值為501 。仔細看運行結果,我們還可以看出virtual函數Log()所起的作用。 總結 本文介紹了Singleton的一種實現方法,可以以正確的次序析構存在依賴關系的一組Singleton實例。我在這的實現主要是基于Evgeniy Gabrilovich的方法,改進了一些功能和使用方便性,並提出一種有派生關系的Singleton類組合的解決方法。這種Singleton的實現方法目前在我的代碼中廣泛使用。 不過要注意這種解決方法並非是 thread-safe的, 關于thread-safe的singleton 實現,可以利用boost.thread來比較方便的實現,也許以后我會再加以介紹的。 這種方法還有一個缺點就是有時需要手工調整phase值,這樣會引起部分代碼重新編譯,所以在設置phase值時可以適當留空,如定義成 10, 20這樣,以后若中間要加一個值,可選為15。 關于本文你有什麼想法,please feel free to contact me。 注釋 [1] Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison Wesley, 1995). (中譯本) 設計模式:可複用面向對象軟件的基礎 (機械工業出版社, 2000). [2] Scott Meyers. Effective C : 50 Specific Ways to Improve Your Programs and Designs (Addison-Wesley Longman, 1998). (中譯本) Effective C 中文版 2nd Edition (華中科技大學出版社, 2001). [3] Evgeniy Gabrilovich. "Destruction-Managed Singleton: A Compound Pattern for Reliable Deallocation of Singletons", C Report, March 2000 . [4] Andrei Alexandrescu. "Mappings between types and values.", C/C Users Journal, 18(10), 2000. 附錄 Listing 1 #include "Singleton.h" #include "DestructionManager.h" class CLogger : private TSingleton時間就是金錢---[ 發問前請先找找舊文章] |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |