全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:1627
推到 Plurk!
推到 Facebook!

[推薦] Singleton 模式在C++中的一種實現

 
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-08-14 15:57:23 IP:61.218.xxx.xxx 未訂閱

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
class        TSingleton
{
        M_Noncopyable(TSingleton)
protected:
        static T*        Instance(void)
        {
                static T*        s_pInstance = new M_TypeOfSingletonPhase(T);
                return s_pInstance;
        }            static void        DestroyInstance(void)
        {
                delete Instance();
        }    protected:
        TSingleton(void) throw()
        {
                M_CAssert(sizeof(TSingleton) == sizeof(C__NULLCLASS__));
                M_CAssert(T::sc_nClassPhase >= CDestructionManager::sc_nClassPhase);                    if (T::sc_nClassPhase > CDestructionManager::sc_nClassPhase)
                {
                        new TDestructor(static_cast(this));        // The Empty Member Optimization !
                }
        }
        ~TSingleton() throw()
        {
        }
};
在此定義里我們可以看到TSingleton的constructor和destructor是protected的,這是因為我們不想讓TSingleton單獨使用,而是讓它做為Singleton類的基類來使用。 T::sc_nClassPhase為Singleton類的一個靜態常量,這有點象類的ID,但並不和ID完全一樣,后面還會有更詳細的說明。 這個類很簡單,可能你唯一奇怪的地方就是TDestructor和CDestructionManager了。我們接著看看它們的實現:
// -- forward declarations --
class CDestructor;
template class TDestructor;    class        CDestructionManager : private TSingleton
{
        M_DefineSingleton(CDestructionManager, 1);
        friend class        CDestructor;
        struct SDestroyObjects;        // forward declaration of nested class
        friend struct        SDestroyObjects;
private:
        void        RegisterDestructor(CDestructor* pDestructor)
        {
                m_arrDestructors.push_back(pDestructor);
        }
        void        DestroyObjects(void);    private:
        CDestructionManager(void);
        ~CDestructionManager()
        {
                stlutil::StlWipe(m_arrDestructors);
        }    private:
        std::vector        m_arrDestructors;    private:
static
struct        SDestroyObjects
{
        ~SDestroyObjects()
        {
                CDestructionManager::Instance()->DestroyObjects();
        }
}        _sm_destroyer;    };    M_RegisterTypeOfSingletonPhase(CDestructionManager)    class        CDestructor
{
        M_Noncopyable(CDestructor)
public:
        CDestructor(int nPhase)
                :
                m_nPhase(nPhase)
        {
                if (nPhase > CDestructionManager::sc_nClassPhase)
                        CDestructionManager::Instance()->RegisterDestructor(this);
        }
        virtual        ~CDestructor() = 0
        {
        }    public:
        virtual void        Destroy(void) = 0;
        int        GetPhase(void) const
        {
                return m_nPhase;
        }    private:
        int        m_nPhase;    public:
struct        SCompare
{
        bool        operator ()(const CDestructor* d1, const CDestructor* d2) const
        {
                return d1->GetPhase() > d2->GetPhase();
        }
};
};    template
class        TDestructor : public CDestructor
{
        typedef CDestructor        baseclass;
public:
        TDestructor(T* pObject)
                :
                baseclass(T::sc_nClassPhase),
                m_pObject(pObject)
        {
        }    public:
        virtual void        Destroy(void)
        {
                m_pObject->DestroyInstance();
        }    private:
        T*        m_pObject;
};    inline void        CDestructionManager::DestroyObjects(void)        // destroy the objects
{
        // sort the destructors in decreasing order
        std::sort(m_arrDestructors.begin(), m_arrDestructors.end(), CDestructor::SCompare());            // destroy the objects
        stlutil::for_all(m_arrDestructors, boost::mem_fn(&CDestructor::Destroy));
}    inline CDestructionManager::CDestructionManager(void)
        :
        baseclass()
        ,m_arrDestructors()
{
        RegisterDestructor(new TDestructor(this));
}    CDestructionManager::SDestroyObjects        CDestructionManager::_sm_destroyer;
這里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        baseclass;\
        friend class        TDestructor;\
        friend class        TSingleton;\
public:\
        using baseclass::Instance;        // make Instance() public    #define M_RegisterTypeOfSingletonPhase(mClass)        M_RegisterTypeOf(mClass::sc_nClassPhase, mClass)
#define M_TypeOfSingletonPhase(mClass)        M_TypeOfID(mClass::sc_nClassPhase)
用了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
struct TSelectType;    #define M_RegisterTypeOf(mID, mT)        \
template<>\
struct        meta::TSelectType\
{\
typedef meta::TType2Type::TOriginalType        TWrap;\
};
#define M_TypeOfID(mID)        meta::TSelectType::TWrap    };
這里利用了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
{
        M_DefineSingleton(CLogger, 500);
protected:
        CLogger(void)
                :
                baseclass()
        {
                ::_tprintf(_T("CLogger created\n"));
        }
        virtual        ~CLogger()
        {
                ::_tprintf(_T("CLogger destroyed\n"));
        }    public:
        virtual void        Log(const TCHAR* pcszLog) = 0
        {
                ::_tprintf(_T("CLogger: %s\n"), pcszLog);
        }
};    class        CLogger2 : public CLogger
{
        friend class        TSingleton;
        typedef CLogger        baseclass;
protected:
        CLogger2(void)
                :
                baseclass()
        {
                ::_tprintf(_T("CLogger2 created\n"));
        }
        virtual        ~CLogger2()
        {
                ::_tprintf(_T("CLogger2 destroyed\n"));
        }    public:
        virtual void        Log(const TCHAR* pcszLog)
        {
                ::_tprintf(_T("CLogger2: %s\n"), pcszLog);
        }
};    M_RegisterTypeOfSingletonPhase(CLogger2)    class        CResource : private TSingleton
{
        M_DefineSingleton(CResource, 501);
private:
        CResource(void)
                :
                baseclass()
        {
                ::_tprintf(_T("CResource created\n"));
        }
        ~CResource()
        {
                CLogger2::Instance()->Log(_T("CResource destructor detected bad status"));
                ::_tprintf(_T("CResource destroyed\n"));
        }    public:
        void        Process(void)
        {
                ::_tprintf(_T("CResource in process\n"));
                CLogger::Instance()->Log(_T("CResource process detected bad status"));
        }
};    M_RegisterTypeOfSingletonPhase(CResource)    void        _tmain(void)
{
        CResource::Instance()->Process();
}    Listing 2    CResource created
CResource in process
CLogger created
CLogger2 created
CLogger2: CResource process detected bad status
CLogger2: CResource destructor detected bad status
CResource destroyed
CLogger2 destroyed
CLogger destroyed    
時間就是金錢---[ 發問前請先找找舊文章]
系統時間:2024-04-24 15:28:10
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!