• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html?ca=drs-cn
            本章首先簡單介紹自定義內存池性能優化的原理,然后列舉軟件開發中常用的內存池的不同類型,并給出具體實現的實例。

            引言

            本 書主要針對的是 C++ 程序的性能優化,深入介紹 C++ 程序性能優化的方法和實例。全書由 4 個篇組成,第 1 篇介紹 C++ 語言的對象模型,該篇是優化 C++ 程序的基礎;第 2 篇主要針對如何優化 C++ 程序的內存使用;第 3 篇介紹如何優化程序的啟動性能;第 4 篇介紹了三類性能優化工具,即內存分析工具、性能分析工具和 I/O 檢測工具,它們是測量程序性能的利器。

            在此我們推出了此書的第 26 章供大家在線瀏覽。更多推薦書籍請訪問 developerWorks 圖書頻道





            回頁首


            6.1 自定義內存池性能優化的原理


            書名:《C++應用程序性能優化》
            作者:馮宏華、徐瑩、程遠、汪磊 等編著
            出版社:電子工業出版社
            出版日期:2007 年 03 月
            ISBN:978-7-121-03831-0
            購買: 中國互動出版網dearbook

            推薦章節:


            如 前所述,讀者已經了解到"堆"和"棧"的區別。而在編程實踐中,不可避免地要大量用到堆上的內存。例如在程序中維護一個鏈表的數據結構時,每次新增或者刪 除一個鏈表的節點,都需要從內存堆上分配或者釋放一定的內存;在維護一個動態數組時,如果動態數組的大小不能滿足程序需要時,也要在內存堆上分配新的內存 空間。

            6.1.1 默認內存管理函數的不足

            利用默認的內存管理函數new/delete或malloc/free在堆上分配和釋放內存會有一些額外的開銷。

            系 統在接收到分配一定大小內存的請求時,首先查找內部維護的內存空閑塊表,并且需要根據一定的算法(例如分配最先找到的不小于申請大小的內存塊給請求者,或 者分配最適于申請大小的內存塊,或者分配最大空閑的內存塊等)找到合適大小的空閑內存塊。如果該空閑內存塊過大,還需要切割成已分配的部分和較小的空閑 塊。然后系統更新內存空閑塊表,完成一次內存分配。類似地,在釋放內存時,系統把釋放的內存塊重新加入到空閑內存塊表中。如果有可能的話,可以把相鄰的空 閑塊合并成較大的空閑塊。

            默認的內存管理函數還考慮到多線程的應用,需要在每次分配和釋放內存時加鎖,同樣增加了開銷。

            可見,如果應用程序頻繁地在堆上分配和釋放內存,則會導致性能的損失。并且會使系統中出現大量的內存碎片,降低內存的利用率。

            默認的分配和釋放內存算法自然也考慮了性能,然而這些內存管理算法的通用版本為了應付更復雜、更廣泛的情況,需要做更多的額外工作。而對于某一個具體的應用程序來說,適合自身特定的內存分配釋放模式的自定義內存池則可以獲得更好的性能。

            6.1.2 內存池的定義和分類

            自 定義內存池的思想通過這個"池"字表露無疑,應用程序可以通過系統的內存分配調用預先一次性申請適當大小的內存作為一個內存池,之后應用程序自己對內存的 分配和釋放則可以通過這個內存池來完成。只有當內存池大小需要動態擴展時,才需要再調用系統的內存分配函數,其他時間對內存的一切操作都在應用程序的掌控 之中。

            應用程序自定義的內存池根據不同的適用場景又有不同的類型。

            從 線程安全的角度來分,內存池可以分為單線程內存池和多線程內存池。單線程內存池整個生命周期只被一個線程使用,因而不需要考慮互斥訪問的問題;多線程內存 池有可能被多個線程共享,因此則需要在每次分配和釋放內存時加鎖。相對而言,單線程內存池性能更高,而多線程內存池適用范圍更廣。

            從內存池可分配內存單元大小來分,可以分為固定內存池和可變內存池。所謂固定內存池是指應用程序每次從內存池中分配出來的內存單元大小事先已經確定,是固定不變的;而可變內存池則每次分配的內存單元大小可以按需變化,應用范圍更廣,而性能比固定內存池要低。

            6.1.3 內存池工作原理示例

            下面以固定內存池為例說明內存池的工作原理,如圖6-1所示。


            圖6-1 固定內存池
            圖6-1  固定內存池

            固定內存池由一系列固定大小的內存塊組成,每一個內存塊又包含了固定數量和大小的內存單元。

            如 圖6-1所示,該內存池一共包含4個內存塊。在內存池初次生成時,只向系統申請了一個內存塊,返回的指針作為整個內存池的頭指針。之后隨著應用程序對內存 的不斷需求,內存池判斷需要動態擴大時,才再次向系統申請新的內存塊,并把所有這些內存塊通過指針鏈接起來。對于操作系統來說,它已經為該應用程序分配了 4個等大小的內存塊。由于是大小固定的,所以分配的速度比較快;而對于應用程序來說,其內存池開辟了一定大小,內存池內部卻還有剩余的空間。

            例 如放大來看第4個內存塊,其中包含一部分內存池塊頭信息和3個大小相等的內存池單元。單元1和單元3是空閑的,單元2已經分配。當應用程序需要通過該內存 池分配一個單元大小的內存時,只需要簡單遍歷所有的內存池塊頭信息,快速定位到還有空閑單元的那個內存池塊。然后根據該塊的塊頭信息直接定位到第1個空閑 的單元地址,把這個地址返回,并且標記下一個空閑單元即可;當應用程序釋放某一個內存池單元時,直接在對應的內存池塊頭信息中標記該內存單元為空閑單元即 可。

            可見與系統管理內存相比,內存池的操作非常迅速,它在性能優化方面的優點主要如下。

            (1)針對特殊情況,例如需要頻繁分配釋放固定大小的內存對象時,不需要復雜的分配算法和多線程保護。也不需要維護內存空閑表的額外開銷,從而獲得較高的性能。

            (2)由于開辟一定數量的連續內存空間作為內存池塊,因而一定程度上提高了程序局部性,提升了程序性能。

            (3)比較容易控制頁邊界對齊和內存字節對齊,沒有內存碎片的問題。





            回頁首


            6.2 一個內存池的實現實例

            本節分析在某個大型應用程序實際應用到的一個內存池實現,并詳細講解其使用方法與工作原理。這是一個應用于單線程環境且分配單元大小固定的內存池,一般用來為執行時會動態頻繁地創建且可能會被多次創建的類對象或者結構體分配內存。

            本節首先講解該內存池的數據結構聲明及圖示,接著描述其原理及行為特征。然后逐一講解實現細節,最后介紹如何在實際程序中應用此內存池,并與使用普通內存函數申請內存的程序性能作比較。

            6.2.1 內部構造

            內存池類MemoryPool的聲明如下:


            class MemoryPool
            {
            private:
            MemoryBlock* pBlock;
            USHORT nUnitSize;
            USHORT nInitSize;
            USHORT nGrowSize;

            public:
            MemoryPool( USHORT nUnitSize,
            USHORT nInitSize = 1024,
            USHORT nGrowSize = 256 );
            ~MemoryPool();

            void* Alloc();
            void Free( void* p );
            };

            MemoryBlock為內存池中附著在真正用來為內存請求分配內存的內存塊頭部的結構體,它描述了與之聯系的內存塊的使用信息:


            struct MemoryBlock
            {
            USHORT nSize;
            USHORT nFree;
            USHORT nFirst;
            USHORT nDummyAlign1;
            MemoryBlock* pNext;
            char aData[1];

            static void* operator new(size_t, USHORT nTypes, USHORT nUnitSize)
            {
            return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize);
            }
            static void operator delete(void *p, size_t)
            {
            ::operator delete (p);
            }

            MemoryBlock (USHORT nTypes = 1, USHORT nUnitSize = 0);
            ~MemoryBlock() {}
            };

            此內存池的數據結構如圖6-2所示。


            圖6-2 內存池的數據結構
            圖6-2  內存池的數據結構

            6.2.2 總體機制

            此內存池的總體機制如下。

            (1) 在運行過程中,MemoryPool內存池可能會有多個用來滿足內存申請請求的內存塊,這些內存塊是從進程堆中開辟的一個較大的連續內存區域,它由一個 MemoryBlock結構體和多個可供分配的內存單元組成,所有內存塊組成了一個內存塊鏈表,MemoryPool的pBlock是這個鏈表的頭。對每 個內存塊,都可以通過其頭部的MemoryBlock結構體的pNext成員訪問緊跟在其后面的那個內存塊。

            (2) 每個內存塊由兩部分組成,即一個MemoryBlock結構體和多個內存分配單元。這些內存分配單元大小固定(由MemoryPool的 nUnitSize表示),MemoryBlock結構體并不維護那些已經分配的單元的信息;相反,它只維護沒有分配的自由分配單元的信息。它有兩個成員 比較重要:nFree和nFirst。nFree記錄這個內存塊中還有多少個自由分配單元,而nFirst則記錄下一個可供分配的單元的編號。每一個自由 分配單元的頭兩個字節(即一個USHORT型值)記錄了緊跟它之后的下一個自由分配單元的編號,這樣,通過利用每個自由分配單元的頭兩個字節,一個 MemoryBlock中的所有自由分配單元被鏈接起來。

            (3)當有新的內存請求到來 時,MemoryPool會通過pBlock遍歷MemoryBlock鏈表,直到找到某個MemoryBlock所在的內存塊,其中還有自由分配單元 (通過檢測MemoryBlock結構體的nFree成員是否大于0)。如果找到這樣的內存塊,取得其MemoryBlock的nFirst值(此為該內 存塊中第1個可供分配的自由單元的編號)。然后根據這個編號定位到該自由分配單元的起始位置(因為所有分配單元大小固定,因此每個分配單元的起始位置都可 以通過編號分配單元大小來偏移定位),這個位置就是用來滿足此次內存申請請求的內存的起始地址。但在返回這個地址前,需要首先將該位置開始的頭兩個字節的 值(這兩個字節值記錄其之后的下一個自由分配單元的編號)賦給本內存塊的MemoryBlock的nFirst成員。這樣下一次的請求就會用這個編號對應 的內存單元來滿足,同時將此內存塊的MemoryBlock的nFree遞減1,然后才將剛才定位到的內存單元的起始位置作為此次內存請求的返回地址返回 給調用者。

            (4)如果從現有的內存塊中找不到一個自由的內存分配單元(當第1次請求內存,以及現有的所有內存 塊中的所有內存分配單元都已經被分配時會發生這種情形),MemoryPool就會從進程堆中申請一個內存塊(這個內存塊包括一個MemoryBlock 結構體,及緊鄰其后的多個內存分配單元,假設內存分配單元的個數為n,n可以取值MemoryPool中的nInitSize或者nGrowSize), 申請完后,并不會立刻將其中的一個分配單元分配出去,而是需要首先初始化這個內存塊。初始化的操作包括設置MemoryBlock的nSize為所有內存 分配單元的大小(注意,并不包括MemoryBlock結構體的大小)、nFree為n-1(注意,這里是n-1而不是n,因為此次新內存塊就是為了滿足 一次新的內存請求而申請的,馬上就會分配一塊自由存儲單元出去,如果設為n-1,分配一個自由存儲單元后無須再將n遞減1),nFirst為1(已經知道 nFirst為下一個可以分配的自由存儲單元的編號。為1的原因與nFree為n-1相同,即立即會將編號為0的自由分配單元分配出去。現在設為1,其后 不用修改nFirst的值),MemoryBlock的構造需要做更重要的事情,即將編號為0的分配單元之后的所有自由分配單元鏈接起來。如前所述,每個 自由分配單元的頭兩個字節用來存儲下一個自由分配單元的編號。另外,因為每個分配單元大小固定,所以可以通過其編號和單元大小(MemoryPool的 nUnitSize成員)的乘積作為偏移值進行定位。現在唯一的問題是定位從哪個地址開始?答案是MemoryBlock的aData[1]成員開始。因 為aData[1]實際上是屬于MemoryBlock結構體的(MemoryBlock結構體的最后一個字節),所以實質上,MemoryBlock結 構體的最后一個字節也用做被分配出去的分配單元的一部分。因為整個內存塊由MemoryBlock結構體和整數個分配單元組成,這意味著內存塊的最后一個 字節會被浪費,這個字節在圖6-2中用位于兩個內存的最后部分的濃黑背景的小塊標識。確定了分配單元的起始位置后,將自由分配單元鏈接起來的工作就很容易 了。即從aData位置開始,每隔nUnitSize大小取其頭兩個字節,記錄其之后的自由分配單元的編號。因為剛開始所有分配單元都是自由的,所以這個 編號就是自身編號加1,即位置上緊跟其后的單元的編號。初始化后,將此內存塊的第1個分配單元的起始地址返回,已經知道這個地址就是aData。

            (5) 當某個被分配的單元因為delete需要回收時,該單元并不會返回給進程堆,而是返回給MemoryPool。返回時,MemoryPool能夠知道該單 元的起始地址。這時,MemoryPool開始遍歷其所維護的內存塊鏈表,判斷該單元的起始地址是否落在某個內存塊的地址范圍內。如果不在所有內存地址范 圍內,則這個被回收的單元不屬于這個MemoryPool;如果在某個內存塊的地址范圍內,那么它會將這個剛剛回收的分配單元加到這個內存塊的 MemoryBlock所維護的自由分配單元鏈表的頭部,同時將其nFree值遞增1。回收后,考慮到資源的有效利用及后續操作的性能,內存池的操作會繼 續判斷:如果此內存塊的所有分配單元都是自由的,那么這個內存塊就會從MemoryPool中被移出并作為一個整體返回給進程堆;如果該內存塊中還有非自 由分配單元,這時不能將此內存塊返回給進程堆。但是因為剛剛有一個分配單元返回給了這個內存塊,即這個內存塊有自由分配單元可供下次分配,因此它會被移到 MemoryPool維護的內存塊的頭部。這樣下次的內存請求到來,MemoryPool遍歷其內存塊鏈表以尋找自由分配單元時,第1次尋找就會找到這個 內存塊。因為這個內存塊確實有自由分配單元,這樣可以減少MemoryPool的遍歷次數。

            綜上所述,每個內 存池(MemoryPool)維護一個內存塊鏈表(單鏈表),每個內存塊由一個維護該內存塊信息的塊頭結構(MemoryBlock)和多個分配單元組 成,塊頭結構MemoryBlock則進一步維護一個該內存塊的所有自由分配單元組成的"鏈表"。這個鏈表不是通過"指向下一個自由分配單元的指針"鏈接 起來的,而是通過"下一個自由分配單元的編號"鏈接起來,這個編號值存儲在該自由分配單元的頭兩個字節中。另外,第1個自由分配單元的起始位置并不是 MemoryBlock結構體"后面的"第1個地址位置,而是MemoryBlock結構體"內部"的最后一個字節aData(也可能不是最后一個,因為 考慮到字節對齊的問題),即分配單元實際上往前面錯了一位。又因為MemoryBlock結構體后面的空間剛好是分配單元的整數倍,這樣依次錯位下去,內 存塊的最后一個字節實際沒有被利用。這么做的一個原因也是考慮到不同平臺的移植問題,因為不同平臺的對齊方式可能不盡相同。即當申請 MemoryBlock大小內存時,可能會返回比其所有成員大小總和還要大一些的內存。最后的幾個字節是為了"補齊",而使得aData成為第1個分配單 元的起始位置,這樣在對齊方式不同的各種平臺上都可以工作。

            6.2.3 細節剖析

            有了上述的總體印象后,本節來仔細剖析其實現細節。

            (1)MemoryPool的構造如下:


            MemoryPool::MemoryPool( USHORT _nUnitSize,
            USHORT _nInitSize, USHORT _nGrowSize )
            {
            pBlock = NULL; ①
            nInitSize = _nInitSize; ②
            nGrowSize = _nGrowSize; ③

            if ( _nUnitSize > 4 )
            nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); ④
            else if ( _nUnitSize <= 2 )
            nUnitSize = 2; ⑤
            else
            nUnitSize = 4;
            }

            從①處可以看出,MemoryPool創建時,并沒有立刻創建真正用來滿足內存申請的內存塊,即內存塊鏈表剛開始時為空。

            ②處和③處分別設置"第1次創建的內存塊所包含的分配單元的個數",及"隨后創建的內存塊所包含的分配單元的個數",這兩個值在MemoryPool創建時通過參數指定,其后在該MemoryPool對象生命周期中一直不變。

            后 面的代碼用來設置nUnitSize,這個值參考傳入的_nUnitSize參數。但是還需要考慮兩個因素。如前所述,每個分配單元在自由狀態時,其頭兩 個字節用來存放"其下一個自由分配單元的編號"。即每個分配單元"最少"有"兩個字節",這就是⑤處賦值的原因。④處是將大于4個字節的大小 _nUnitSize往上"取整到"大于_nUnitSize的最小的MEMPOOL_ ALIGNMENT的倍數(前提是MEMPOOL_ALIGNMENT為2的倍數)。如_nUnitSize為11 時,MEMPOOL_ALIGNMENT為8,nUnitSize為16;MEMPOOL_ALIGNMENT為4,nUnitSize為 12;MEMPOOL_ALIGNMENT為2,nUnitSize為12,依次類推。

            (2)當向MemoryPool提出內存請求時:


            void* MemoryPool::Alloc()
            {
            if ( !pBlock ) ①
            {
            ……
            }

            MemoryBlock* pMyBlock = pBlock;
            while (pMyBlock && !pMyBlock->nFree )②
            pMyBlock = pMyBlock->pNext;

            if ( pMyBlock ) ③
            {
            char* pFree = pMyBlock->aData+(pMyBlock->nFirst*nUnitSize);
            pMyBlock->nFirst = *((USHORT*)pFree);

            pMyBlock->nFree--;
            return (void*)pFree;
            }
            else ④
            {
            if ( !nGrowSize )
            return NULL;

            pMyBlock = new(nGrowSize, nUnitSize) FixedMemBlock(nGrowSize, nUnitSize);
            if ( !pMyBlock )
            return NULL;

            pMyBlock->pNext = pBlock;
            pBlock = pMyBlock;

            return (void*)(pMyBlock->aData);
            }

            }

            MemoryPool滿足內存請求的步驟主要由四步組成。

            ① 處首先判斷內存池當前內存塊鏈表是否為空,如果為空,則意味著這是第1次內存申請請求。這時,從進程堆中申請一個分配單元個數為nInitSize的內存 塊,并初始化該內存塊(主要初始化MemoryBlock結構體成員,以及創建初始的自由分配單元鏈表,下面會詳細分析其代碼)。如果該內存塊申請成功, 并初始化完畢,返回第1個分配單元給調用函數。第1個分配單元以MemoryBlock結構體內的最后一個字節為起始地址。

            ②處的作用是當內存池中已有內存塊(即內存塊鏈表不為空)時遍歷該內存塊鏈表,尋找還有"自由分配單元"的內存塊。

            ③ 處檢查如果找到還有自由分配單元的內存塊,則"定位"到該內存塊現在可以用的自由分配單元處。"定位"以MemoryBlock結構體內的最后一個字節位 置aData為起始位置,以MemoryPool的nUnitSize為步長來進行。找到后,需要修改MemoryBlock的nFree信息(剩下來的 自由分配單元比原來減少了一個),以及修改此內存塊的自由存儲單元鏈表的信息。在找到的內存塊中,pMyBlock->nFirst為該內存塊中自 由存儲單元鏈表的表頭,其下一個自由存儲單元的編號存放在pMyBlock->nFirst指示的自由存儲單元(亦即剛才定位到的自由存儲單元)的 頭兩個字節。通過剛才定位到的位置,取其頭兩個字節的值,賦給pMyBlock->nFirst,這就是此內存塊的自由存儲單元鏈表的新的表頭,即 下一次分配出去的自由分配單元的編號(如果nFree大于零的話)。修改維護信息后,就可以將剛才定位到的自由分配單元的地址返回給此次申請的調用函數。 注意,因為這個分配單元已經被分配,而內存塊無須維護已分配的分配單元,因此該分配單元的頭兩個字節的信息已經沒有用處。換個角度看,這個自由分配單元返 回給調用函數后,調用函數如何處置這塊內存,內存池無從知曉,也無須知曉。此分配單元在返回給調用函數時,其內容對于調用函數來說是無意義的。因此幾乎可 以肯定調用函數在用這個單元的內存時會覆蓋其原來的內容,即頭兩個字節的內容也會被抹去。因此每個存儲單元并沒有因為需要鏈接而引入多余的維護信息,而是 直接利用單元內的頭兩個字節,當其分配后,頭兩個字節也可以被調用函數利用。而在自由狀態時,則用來存放維護信息,即下一個自由分配單元的編號,這是一個 有效利用內存的好例子。

            ④處表示在②處遍歷時,沒有找到還有自由分配單元的內存塊,這時,需要重新向進程堆申 請一個內存塊。因為不是第一次申請內存塊,所以申請的內存塊包含的分配單元個數為nGrowSize,而不再是nInitSize。與①處相同,先做這個 新申請內存塊的初始化工作,然后將此內存塊插入MemoryPool的內存塊鏈表的頭部,再將此內存塊的第1個分配單元返回給調用函數。將此新內存塊插入 內存塊鏈表的頭部的原因是該內存塊還有很多可供分配的自由分配單元(除非nGrowSize等于1,這應該不太可能。因為內存池的含義就是一次性地從進程 堆中申請一大塊內存,以供后續的多次申請),放在頭部可以使得在下次收到內存申請時,減少②處對內存塊的遍歷時間。

            可以用圖6-2的MemoryPool來展示MemoryPool::Alloc的過程。圖6-3是某個時刻MemoryPool的內部狀態。


            圖6-3 某個時刻MemoryPool的內部狀態
            圖6-3  某個時刻memorypool的內部狀態

            因 為MemoryPool的內存塊鏈表不為空,因此會遍歷其內存塊鏈表。又因為第1個內存塊里有自由的分配單元,所以會從第1個內存塊中分配。檢查 nFirst,其值為m,這時pBlock->aData+(pBlock->nFirst*nUnitSize)定位到編號為m的自由分配 單元的起始位置(用pFree表示)。在返回pFree之前,需要修改此內存塊的維護信息。首先將nFree遞減1,然后取得pFree處開始的頭兩個字 節的值(需要說明的是,這里aData處值為k。其實不是這一個字節。而是以aData和緊跟其后的另外一個字節合在一起構成的一個USHORT的值,不 可誤會)。發現為k,這時修改pBlock的nFirst為k。然后,返回pFree。此時MemoryPool的結構如圖6-4所示。


            圖6-4 MemoryPool的結構
            圖6-4  memorypool的結構

            可以看到,原來的第1個可供分配的單元(m編號處)已經顯示為被分配的狀態。而pBlock的nFirst已經指向原來m單元下一個自由分配單元的編號,即k。

            (3)MemoryPool回收內存時:


            void MemoryPool::Free( void* pFree )
            {
            ……

            MemoryBlock* pMyBlock = pBlock;

            while ( ((ULONG)pMyBlock->aData > (ULONG)pFree) ||
            ((ULONG)pFree >= ((ULONG)pMyBlock->aData + pMyBlock->nSize)) )①
            {
            ……
            }

            pMyBlock->nFree++; ②
            *((USHORT*)pFree) = pMyBlock->nFirst; ③
            pMyBlock->nFirst = (USHORT)(((ULONG)pFree-(ULONG)(pBlock->aData)) / nUnitSize);④

            if (pMyBlock->nFree*nUnitSize == pMyBlock->nSize )⑤
            {
            ……
            }
            else
            {
            ……
            }
            }

            如前所述,回收分配單元時,可能會將整個內存塊返回給進程堆,也可能將被回收分配單元所屬的內存塊移至內存池的內存塊鏈表的頭部。這兩個操作都需要修改鏈表結構。這時需要知道該內存塊在鏈表中前一個位置的內存塊。

            ①處遍歷內存池的內存塊鏈表,確定該待回收分配單元(pFree)落在哪一個內存塊的指針范圍內,通過比較指針值來確定。

            運 行到②處,pMyBlock即找到的包含pFree所指向的待回收分配單元的內存塊(當然,這時應該還需要檢查pMyBlock為NULL時的情形,即 pFree不屬于此內存池的范圍,因此不能返回給此內存池,讀者可以自行加上)。這時將pMyBlock的nFree遞增1,表示此內存塊的自由分配單元 多了一個。

            ③處用來修改該內存塊的自由分配單元鏈表的信息,它將這個待回收分配單元的頭兩個字節的值指向該內存塊原來的第一個可分配的自由分配單元的編號。

            ④處將pMyBlock的nFirst值改變為指向這個待回收分配單元的編號,其編號通過計算此單元的起始位置相對pMyBlock的aData位置的差值,然后除以步長(nUnitSize)得到。

            實 質上,③和④兩步的作用就是將此待回收分配單元"真正回收"。值得注意的是,這兩步實際上是使得此回收單元成為此內存塊的下一個可分配的自由分配單元,即 將它放在了自由分配單元鏈表的頭部。注意,其內存地址并沒有發生改變。實際上,一個分配單元的內存地址無論是在分配后,還是處于自由狀態時,一直都不會變 化。變化的只是其狀態(已分配/自由),以及當其處于自由狀態時在自由分配單元鏈表中的位置。

            ⑤處檢查當回收完畢后,包含此回收單元的內存塊的所有單元是否都處于自由狀態,且此內存是否處于內存塊鏈表的頭部。如果是,將此內存塊整個的返回給進程堆,同時修改內存塊鏈表結構。

            注 意,這里在判斷一個內存塊的所有單元是否都處于自由狀態時,并沒有遍歷其所有單元,而是判斷nFree乘以nUnitSize是否等于nSize。 nSize是內存塊中所有分配單元的大小,而不包括頭部MemoryBlock結構體的大小。這里可以看到其用意,即用來快速檢查某個內存塊中所有分配單 元是否全部處于自由狀態。因為只需結合nFree和nUnitSize來計算得出結論,而無須遍歷和計算所有自由狀態的分配單元的個數。

            另 外還需注意的是,這里并不能比較nFree與nInitSize或nGrowSize的大小來判斷某個內存塊中所有分配單元都為自由狀態,這是因為第1次 分配的內存塊(分配單元個數為nInitSize)可能被移到鏈表的后面,甚至可能在移到鏈表后面后,因為某個時間其所有單元都處于自由狀態而被整個返回 給進程堆。即在回收分配單元時,無法判定某個內存塊中的分配單元個數到底是nInitSize還是nGrowSize,也就無法通過比較nFree與 nInitSize或nGrowSize的大小來判斷一個內存塊的所有分配單元是否都為自由狀態。

            以上面分配后的內存池狀態作為例子,假設這時第2個內存塊中的最后一個單元需要回收(已被分配,假設其編號為m,pFree指針指向它),如圖6-5所示。

            不 難發現,這時nFirst的值由原來的0變為m。即此內存塊下一個被分配的單元是m編號的單元,而不是0編號的單元(最先分配的是最新回收的單元,從這一 點看,這個過程與棧的原理類似,即先進后出。只不過這里的"進"意味著"回收",而"出"則意味著"分配")。相應地,m的"下一個自由單元"標記為0, 即內存塊原來的"下一個將被分配出去的單元",這也表明最近回收的分配單元被插到了內存塊的"自由分配單元鏈表"的頭部。當然,nFree遞增1。


            圖6-5 分配后的內存池狀態
            圖6-5  分配后的內存池狀態

            處理至⑥處之前,其狀態如圖6-6所示。


            圖6-6 處理至⑥處之前的內存池狀態
            圖6-6  處理至⑥處之前的內存池狀態

            這 里需要注意的是,雖然pFree被"回收",但是pFree仍然指向m編號的單元,這個單元在回收過程中,其頭兩個字節被覆寫,但其他部分的內容并沒有改 變。而且從整個進程的內存使用角度來看,這個m編號的單元的狀態仍然是"有效的"。因為這里的"回收"只是回收給了內存池,而并沒有回收給進程堆,因此程 序仍然可以通過pFree訪問此單元。但是這是一個很危險的操作,因為首先該單元在回收過程中頭兩個字節已被覆寫,并且該單元可能很快就會被內存池重新分 配。因此回收后通過pFree指針對這個單元的訪問都是錯誤的,讀操作會讀到錯誤的數據,寫操作則可能會破壞程序中其他地方的數據,因此需要格外小心。

            接著,需要判斷該內存塊的內部使用情況,及其在內存塊鏈表中的位置。如果該內存塊中省略號"……"所表示的其他部分中還有被分配的單元,即nFree乘以nUnitSize不等于nSize。因為此內存塊不在鏈表頭,因此還需要將其移到鏈表頭部,如圖6-7所示。


            圖6-7 因回收引起的MemoryBlock移動
            圖6-7  因回收引起的memoryblock移動

            如果該內存塊中省略號"……"表示的其他部分中全部都是自由分配單元,即nFree乘以nUnitSize等于nSize。因為此內存塊不在鏈表頭,所以此時需要將此內存塊整個回收給進程堆,回收后內存池的結構如圖6-8所示。


            圖6-8 回收后內存池的結構
            圖6-8  回收后內存池的結構

            一個內存塊在申請后會初始化,主要是為了建立最初的自由分配單元鏈表,下面是其詳細代碼:


            MemoryBlock::MemoryBlock (USHORT nTypes, USHORT nUnitSize)
            : nSize (nTypes * nUnitSize),
            nFree (nTypes - 1), ④
            nFirst (1), ⑤
            pNext (0)
            {
            char * pData = aData; ①
            for (USHORT i = 1; i < nTypes; i++) ②
            {
            *reinterpret_cast<USHORT*>(pData) = i; ③
            pData += nUnitSize;
            }
            }

            這里可以看到,①處pData的初值是 aData,即0編號單元。但是②處的循環中i卻是從1開始,然后在循環內部的③處將pData的頭兩個字節值置為i。即0號單元的頭兩個字節值為1,1 號單元的頭兩個字節值為2,一直到(nTypes-2)號單元的頭兩個字節值為(nTypes-1)。這意味著內存塊初始時,其自由分配單元鏈表是從0號 開始。依次串聯,一直到倒數第2個單元指向最后一個單元。

            還需要注意的是,在其初始化列表中,nFree初始 化為nTypes-1(而不是nTypes),nFirst初始化為1(而不是0)。這是因為第1個單元,即0編號單元構造完畢后,立刻會被分配。另外注 意到最后一個單元初始并沒有設置頭兩個字節的值,因為該單元初始在本內存塊中并沒有下一個自由分配單元。但是從上面例子中可以看到,當最后一個單元被分配 并回收后,其頭兩個字節會被設置。

            圖6-9所示為一個內存塊初始化后的狀態。


            圖6-9 一個內存塊初始化后的狀態
            圖6-9  一個內存塊初始化后的狀態

            當內存池析構時,需要將內存池的所有內存塊返回給進程堆:


            MemoryPool::~MemoryPool()
            {
            MemoryBlock* pMyBlock = pBlock;
            while ( pMyBlock )
            {
            ……
            }
            }

            6.2.4 使用方法

            分 析內存池的內部原理后,本節說明如何使用它。從上面的分析可以看到,該內存池主要有兩個對外接口函數,即Alloc和Free。Alloc返回所申請的分 配單元(固定大小內存),Free則回收傳入的指針代表的分配單元的內存給內存池。分配的信息則通過MemoryPool的構造函數指定,包括分配單元大 小、內存池第1次申請的內存塊中所含分配單元的個數,以及內存池后續申請的內存塊所含分配單元的個數等。

            綜上 所述,當需要提高某些關鍵類對象的申請/回收效率時,可以考慮將該類所有生成對象所需的空間都從某個這樣的內存池中開辟。在銷毀對象時,只需要返回給該內 存池。"一個類的所有對象都分配在同一個內存池對象中"這一需求很自然的設計方法就是為這樣的類聲明一個靜態內存池對象,同時為了讓其所有對象都從這個內 存池中開辟內存,而不是缺省的從進程堆中獲得,需要為該類重載一個new運算符。因為相應地,回收也是面向內存池,而不是進程的缺省堆,還需要重載一個 delete運算符。在new運算符中用內存池的Alloc函數滿足所有該類對象的內存請求,而銷毀某對象則可以通過在delete運算符中調用內存池的 Free完成。

            6.2.5 性能比較

            為 了測試利用內存池后的效果,通過一個很小的測試程序可以發現采用內存池機制后耗時為297 ms。而沒有采用內存池機制則耗時625 ms,速度提高了52.48%。速度提高的原因可以歸結為幾點,其一,除了偶爾的內存申請和銷毀會導致從進程堆中分配和銷毀內存塊外,絕大多數的內存申請 和銷毀都由內存池在已經申請到的內存塊中進行,而沒有直接與進程堆打交道,而直接與進程堆打交道是很耗時的操作;其二,這是單線程環境的內存池,可以看到 內存池的Alloc和Free操作中并沒有加線程保護措施。因此如果類A用到該內存池,則所有類A對象的創建和銷毀都必須發生在同一個線程中。但如果類A 用到內存池,類B也用到內存池,那么類A的使用線程可以不必與類B的使用線程是同一個線程。

            另外,在第1章中已經討論過,因為內存池技術使得同類型的對象分布在相鄰的內存區域,而程序會經常對同一類型的對象進行遍歷操作。因此在程序運行過程中發生的缺頁應該會相應少一些,但這個一般只能在真實的復雜應用環境中進行驗證。





            回頁首


            6.3 本章小結

            內 存的申請和釋放對一個應用程序的整體性能影響極大,甚至在很多時候成為某個應用程序的瓶頸。消除內存申請和釋放引起的瓶頸的方法往往是針對內存使用的實際 情況提供一個合適的內存池。內存池之所以能夠提高性能,主要是因為它能夠利用應用程序的實際內存使用場景中的某些"特性"。比如某些內存申請與釋放肯定發 生在一個線程中,某種類型的對象生成和銷毀與應用程序中的其他類型對象要頻繁得多,等等。針對這些特性,可以為這些特殊的內存使用場景提供量身定做的內存 池。這樣能夠消除系統提供的缺省內存機制中,對于該實際應用場景中的不必要的操作,從而提升應用程序的整體性能。



            作者簡介


            馮 宏華,清華大學計算機科學與技術系碩士。IBM 中國開發中心高級軟件工程師。 2003 年 12 月加入 IBM 中國開發中心,主要從事 IBM 產品的開發、性能優化等工作。興趣包括 C/C++ 應用程序性能調優,Windows 應用程序開發,Web 應用程序開發等。



            徐 瑩,山東大學計算機科學與技術系碩士。2003 年 4 月加入 IBM 中國開發中心,現任 IBM 中國開發中心開發經理,一直從事IBM軟件產品在多個操作系統平臺上的開發工作。曾參與 IBM 產品在 Windows 和 Linux 平臺上的性能優化工作,對 C/C++ 編程語言和跨平臺的大型軟件系統的開發有較豐富的經驗。



            程 遠,北京大學計算機科學與技術系碩士。IBM 中國開發中心高級軟件工程師。2003 年加入 IBM 中國開發中心,主要從事IBM Productivity Tools 產品的開發、性能優化等工作。興趣包括 C/C++ 編程語言,軟件性能工程,Windows/Linux 平臺性能測試優化工具等。



            汪 磊,北京航空航天大學計算機科學與技術系碩士,目前是 IBM 中國軟件開發中心高級軟件工程師。從 2002 年 12 月加入 IBM 中國開發中心至今一直從事旨在提高企業生產效率的應用軟件開發。興趣包括 C\C++ 應用程序的性能調優,Java 應用程序的性能調優。



            posted @ 2008-11-17 16:37 micheal's tech 閱讀(408) | 評論 (0)編輯 收藏

            The "Double-Checked Locking is Broken" Declaration

            Signed by : David Bacon (IBM Research) Joshua Bloch (Javasoft), Jeff Bogda, Cliff Click (Hotspot JVM project), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen, Jeremy Manson, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun Sirer

            Double-Checked Locking is widely cited and used as an efficient method for implementing lazy initialization in a multithreaded environment.

            Unfortunately, it will not work reliably in a platform independent way when implemented in Java, without additional synchronization. When implemented in other languages, such as C++, it depends on the memory model of the processor, the reorderings performed by the compiler and the interaction between the compiler and the synchronization library. Since none of these are specified in a language such as C++, little can be said about the situations in which it will work. Explicit memory barriers can be used to make it work in C++, but these barriers are not available in Java.

            To first explain the desired behavior, consider the following code:

            // Single threaded version
            class Foo {
            private Helper helper = null;
            public Helper getHelper() {
            if (helper == null)
            helper = new Helper();
            return helper;
            }
            // other functions and members...
            }

            If this code was used in a multithreaded context, many things could go wrong. Most obviously, two or more Helper objects could be allocated. (We'll bring up other problems later). The fix to this is simply to synchronize the getHelper() method:

            // Correct multithreaded version
            class Foo {
            private Helper helper = null;
            public synchronized Helper getHelper() {
            if (helper == null)
            helper = new Helper();
            return helper;
            }
            // other functions and members...
            }

            The code above performs synchronization every time getHelper() is called. The double-checked locking idiom tries to avoid synchronization after the helper is allocated:

            // Broken multithreaded version
            // "Double-Checked Locking" idiom
            class Foo {
            private Helper helper = null;
            public Helper getHelper() {
            if (helper == null)
            synchronized(this) {
            if (helper == null)
            helper = new Helper();
            }
            return helper;
            }
            // other functions and members...
            }

            Unfortunately, that code just does not work in the presence of either optimizing compilers or shared memory multiprocessors.

            It doesn't work

            There are lots of reasons it doesn't work. The first couple of reasons we'll describe are more obvious. After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. Your fixes will not work: there are more subtle reasons why your fix won't work. Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons.

            Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.

            The first reason it doesn't work

            The most obvious reason it doesn't work it that the writes that initialize the Helper object and the write to the helper field can be done or perceived out of order. Thus, a thread which invokes getHelper() could see a non-null reference to a helper object, but see the default values for fields of the helper object, rather than the values set in the constructor.

            If the compiler inlines the call to the constructor, then the writes that initialize the object and the write to the helper field can be freely reordered if the compiler can prove that the constructor cannot throw an exception or perform synchronization.

            Even if the compiler does not reorder those writes, on a multiprocessor the processor or the memory system may reorder those writes, as perceived by a thread running on another processor.

            Doug Lea has written a more detailed description of compiler-based reorderings.

            A test case showing that it doesn't work

            Paul Jakubik found an example of a use of double-checked locking that did not work correctly. A slightly cleaned up version of that code is available here.

            When run on a system using the Symantec JIT, it doesn't work. In particular, the Symantec JIT compiles

            singletons[i].reference = new Singleton();

            to the following (note that the Symantec JIT using a handle-based object allocation system).

            0206106A   mov         eax,0F97E78h
            0206106F call 01F6B210 ; allocate space for
            ; Singleton, return result in eax
            02061074 mov dword ptr [ebp],eax ; EBP is &singletons[i].reference
            ; store the unconstructed object here.
            02061077 mov ecx,dword ptr [eax] ; dereference the handle to
            ; get the raw pointer
            02061079 mov dword ptr [ecx],100h ; Next 4 lines are
            0206107F mov dword ptr [ecx+4],200h ; Singleton's inlined constructor
            02061086 mov dword ptr [ecx+8],400h
            0206108D mov dword ptr [ecx+0Ch],0F84030h

            As you can see, the assignment to singletons[i].reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C++ (since neither of them have a memory model).

            A fix that doesn't work

            Given the explanation above, a number of people have suggested the following code:

            // (Still) Broken multithreaded version
            // "Double-Checked Locking" idiom
            class Foo {
            private Helper helper = null;
            public Helper getHelper() {
            if (helper == null) {
            Helper h;
            synchronized(this) {
            h = helper;
            if (h == null)
            synchronized (this) {
            h = new Helper();
            } // release inner synchronization lock
            helper = h;
            }
            }
            return helper;
            }
            // other functions and members...
            }

            This code puts construction of the Helper object inside an inner synchronized block. The intuitive idea here is that there should be a memory barrier at the point where synchronization is released, and that should prevent the reordering of the initialization of the Helper object and the assignment to the field helper.

            Unfortunately, that intuition is absolutely wrong. The rules for synchronization don't work that way. The rule for a monitorexit (i.e., releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. However, there is no rule which says that actions after the monitorexit may not be done before the monitor is released. It is perfectly reasonable and legal for the compiler to move the assignment helper = h; inside the synchronized block, in which case we are back where we were previously. Many processors offer instructions that perform this kind of one-way memory barrier. Changing the semantics to require releasing a lock to be a full memory barrier would have performance penalties.

            More fixes that don't work

            There is something you can do to force the writer to perform a full bidirectional memory barrier. This is gross, inefficient, and is almost guaranteed not to work once the Java Memory Model is revised. Do not use this. In the interests of science, Do not use it.

            However , even with a full memory barrier being performed by the thread that initializes the helper object, it still doesn't work.

            The problem is that on some systems, the thread which sees a non-null value for the helper field also needs to perform memory barriers.

            Why? Because processors have their own locally cached copies of memory. On some processors, unless the processor performs a cache coherence instruction (e.g., a memory barrier), reads can be performed out of stale locally cached copies, even if other processors used memory barriers to force their writes into global memory.

            I've created a separate web page with a discussion of how this can actually happen on an Alpha processor.

            Is it worth the trouble?

            For most applications, the cost of simply making the getHelper() method synchronized is not high. You should only consider this kind of detailed optimizations if you know that it is causing a substantial overhead for an application.

            Very often, more high level cleverness, such as using the builtin mergesort rather than handling exchange sort (see the SPECJVM DB benchmark) will have much more impact.

            Making it work for static singletons

            If the singleton you are creating is static (i.e., there will only be one Helper created), as opposed to a property of another object (e.g., there will be one Helper for each Foo object, there is a simple and elegant solution.

            Just define the singleton as a static field in a separate class. The semantics of Java guarantee that the field will not be initialized until the field is referenced, and that any thread which accesses the field will see all of the writes resulting from initializing that field.

            class HelperSingleton {
            static Helper singleton = new Helper();
            }

            It will work for 32-bit primitive values

            Although the double-checked locking idiom cannot be used for references to objects, it can work for 32-bit primitive values (e.g., int's or float's). Note that it does not work for long's or double's, since unsynchronized reads/writes of 64-bit primitives are not guaranteed to be atomic.

            // Correct Double-Checked Locking for 32-bit primitives
            class Foo {
            private int cachedHashCode = 0;
            public int hashCode() {
            int h = cachedHashCode;
            if (h == 0)
            synchronized(this) {
            if (cachedHashCode != 0) return cachedHashCode;
            h = computeHashCode();
            cachedHashCode = h;
            }
            return h;
            }
            // other functions and members...
            }

            In fact, assuming that the computeHashCode function always returned the same result and had no side effects (i.e., idempotent), you could even get rid of all of the synchronization.

            // Lazy initialization 32-bit primitives
            // Thread-safe if computeHashCode is idempotent
            class Foo {
            private int cachedHashCode = 0;
            public int hashCode() {
            int h = cachedHashCode;
            if (h == 0) {
            h = computeHashCode();
            cachedHashCode = h;
            }
            return h;
            }
            // other functions and members...
            }

            Making it work with explicit memory barriers

            It is possible to make the double checked locking pattern work if you have explicit memory barrier instructions. For example, if you are programming in C++, you can use the code from Doug Schmidt et al.'s book:

            // C++ implementation with explicit memory barriers
            // Should work on any platform, including DEC Alphas
            // From "Patterns for Concurrent and Distributed Objects",
            // by Doug Schmidt
            template <class TYPE, class LOCK> TYPE *
            Singleton<TYPE, LOCK>::instance (void) {
            // First check
            TYPE* tmp = instance_;
            // Insert the CPU-specific memory barrier instruction
            // to synchronize the cache lines on multi-processor.
            asm ("memoryBarrier");
            if (tmp == 0) {
            // Ensure serialization (guard
            // constructor acquires lock_).
            Guard<LOCK> guard (lock_);
            // Double check.
            tmp = instance_;
            if (tmp == 0) {
            tmp = new TYPE;
            // Insert the CPU-specific memory barrier instruction
            // to synchronize the cache lines on multi-processor.
            asm ("memoryBarrier");
            instance_ = tmp;
            }
            return tmp;
            }

            Fixing Double-Checked Locking using Thread Local Storage

            Alexander Terekhov (TEREKHOV@de.ibm.com) came up clever suggestion for implementing double checked locking using thread local storage. Each thread keeps a thread local flag to determine whether that thread has done the required synchronization.
              class Foo {
            /** If perThreadInstance.get() returns a non-null value, this thread
            has done synchronization needed to see initialization
            of helper */
            private final ThreadLocal perThreadInstance = new ThreadLocal();
            private Helper helper = null;
            public Helper getHelper() {
            if (perThreadInstance.get() == null) createHelper();
            return helper;
            }
            private final void createHelper() {
            synchronized(this) {
            if (helper == null)
            helper = new Helper();
            }
            // Any non-null value would do as the argument here
            perThreadInstance.set(perThreadInstance);
            }
            }

            The performance of this technique depends quite a bit on which JDK implementation you have. In Sun's 1.2 implementation, ThreadLocal's were very slow. They are significantly faster in 1.3, and are expected to be faster still in 1.4. Doug Lea analyzed the performance of some techniques for implementing lazy initialization.

            Under the new Java Memory Model

            As of JDK5, there is a new Java Memory Model and Thread specification.

            Fixing Double-Checked Locking using Volatile

            JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write. See this entry in Jeremy Manson's blog for more details.

            With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under JDK4 and earlier.

            // Works with acquire/release semantics for volatile
            // Broken under current semantics for volatile
            class Foo {
            private volatile Helper helper = null;
            public Helper getHelper() {
            if (helper == null) {
            synchronized(this) {
            if (helper == null)
            helper = new Helper();
            }
            }
            return helper;
            }
            }

            Double-Checked Locking Immutable Objects

            If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic.

            Descriptions of double-check idiom


            posted @ 2008-10-23 14:25 micheal's tech 閱讀(637) | 評論 (0)編輯 收藏

            0 引言

            0.1 目的

                   本文檔給出設計模式之——AbstractFactory模式的簡化詮釋,并給出其C++實現。

            0.2 說明

            Project

            Design Pattern Explanation(By K_Eckel)

            Authorization

            Free Distributed but Ownership Reserved

            Date

            Test Bed

            MS Visual C++ 6.0

            0.3 參考

                   在本文檔的寫作中,參考了以下的資源,在此列出表示感謝:

            u       書籍

            [GoF 2000]:GoF,Design Patterns-Elements of Reusable Object-Oriented Software

            Addison-Wesley 2000/9.

                    [Martine 2003]:Robert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.

            0.4 聯系作者

            Author

            K_Eckel

            State

            Candidate for Master’s Degree School of

            E_mail

            frwei@whu.edu.cn  

            2 AbstractFactory模式

            2.1 問題

                   假設我們要開發一款游戲,當然為了吸引更多的人玩,游戲難度不能太大(讓大家都沒有信心了,估計游戲也就沒有前途了),但是也不能太簡單(沒有挑戰性也不符合玩家的心理)。于是我們就可以采用這樣一種處理策略:為游戲設立等級,初級、中級、高級甚至有BT級。假設也是過關的游戲,每個關卡都有一些怪物(monster)守著,玩家要把這些怪 物干掉才可以過關。作為開發者,我們就不得不創建怪物的類,然后初級怪物、中級怪物等都繼承自怪物類(當然不同種類的則需要另創建類,但是模式相同)。在 每個關卡,我們都要創建怪物的實例,例如初級就創建初級怪物(有很多種類)、中級創建中級怪物等。可以想象在這個系統中,將會有成千上萬的怪物實例要創 建,問題是還要保證創建的時候不會出錯:初級不能創建BT級的怪物(玩家就郁悶了,玩家一郁悶,游戲也就掛掛了),反之也不可以。

                   AbstractFactory模式就是用來解決這類問題的:要創建一組相關或者相互依賴的對象。

            2.2 模式選擇

                   AbstractFactory模式典型的結構圖為:


            2-1AbstractFactoryPattern結構圖

                   AbstractFactory模式關鍵就是將這一組對象的創建封裝到一個用于創建對象的類(ConcreteFactory)中,維護這樣一個創建類總比維護n多相關對象的創建過程要簡單的多。

            2.3 實現

                   AbstractFactory模式的實現比較簡單,這里為了方便初學者的學習和參考,將給出完整的實現代碼(所有代碼采用C++實現,并在VC 6.0下測試運行)。

            代碼片斷1Product.h
            //Product.h

            #ifndef _PRODUCT_H_
            #define _PRODUCT_H_

            class AbstractProductA
            {
            public:
             virtual ~AbstractProductA();

            protected:
             AbstractProductA();

            private:

            };

            class AbstractProductB
            {
            public:
             virtual ~AbstractProductB();

            protected:
             AbstractProductB();

            private:

            };

            class ProductA1:public AbstractProductA
            {
            public:
             ProductA1();

             ~ProductA1();

            protected:

            private:

            };

            class ProductA2:public AbstractProductA
            {
            public:
             ProductA2();

             ~ProductA2();

            protected:

            private:

            };

            class ProductB1:public AbstractProductB
            {
            public:
             ProductB1();

             ~ProductB1();

            protected:

            private:

            };

            class ProductB2:public AbstractProductB
            {
            public:
             ProductB2();

             ~ProductB2();

            protected:

            private:

            };

            #endif //~_PRODUCT_H_

            代碼片斷2Product.cpp
            //Product.cpp

            #include "Product.h"

            #include <iostream>
            using namespace std;

            AbstractProductA::AbstractProductA()
            {

            }

            AbstractProductA::~AbstractProductA()
            {

            }

            AbstractProductB::AbstractProductB()
            {

            }

            AbstractProductB::~AbstractProductB()
            {

            }

            ProductA1::ProductA1()
            {
             cout<<"ProductA1..."<<endl;
            }

            ProductA1::~ProductA1()
            {

            }

            ProductA2::ProductA2()
            {
             cout<<"ProductA2..."<<endl;
            }

            ProductA2::~ProductA2()
            {

            }

            ProductB1::ProductB1()
            {
             cout<<"ProductB1..."<<endl;
            }

            ProductB1::~ProductB1()
            {

            }

            ProductB2::ProductB2()
            {
             cout<<"ProductB2..."<<endl;
            }

            ProductB2::~ProductB2()
            {

            }

            代碼片斷3AbstractFactory.h
            //AbstractFactory.h

            #ifndef _ABSTRACTFACTORY_H_
            #define _ABSTRACTFACTORY_H_

            class AbstractProductA;
            class AbstractProductB;

            class AbstractFactory
            {
            public:
             virtual ~AbstractFactory();

             virtual AbstractProductA* CreateProductA() = 0;

             virtual AbstractProductB* CreateProductB() = 0;

            protected:
             AbstractFactory();

            private:

            };

            class ConcreteFactory1:public AbstractFactory
            {
            public:
             ConcreteFactory1();

             ~ConcreteFactory1();

             AbstractProductA* CreateProductA();

             AbstractProductB* CreateProductB();

            protected:

            private:

            };

            class ConcreteFactory2:public AbstractFactory
            {
            public:
             ConcreteFactory2();

             ~ConcreteFactory2();

             AbstractProductA* CreateProductA();

             AbstractProductB* CreateProductB();

            protected:

            private:

            };
            #endif //~_ABSTRACTFACTORY_H_

            代碼片斷4AbstractFactory.cpp
            //AbstractFactory.cpp

            #include "AbstractFactory.h"
            #include "Product.h"

            #include <iostream>
            using namespace std;

            AbstractFactory::AbstractFactory()
            {

            }

            AbstractFactory::~AbstractFactory()
            {

            }

            ConcreteFactory1::ConcreteFactory1()
            {

            }

            ConcreteFactory1::~ConcreteFactory1()
            {

            }

            AbstractProductA* ConcreteFactory1::CreateProductA()
            {
             return new ProductA1();
            }

            AbstractProductB* ConcreteFactory1::CreateProductB()
            {
             return new ProductB1();
            }

            ConcreteFactory2::ConcreteFactory2()
            {

            }

            ConcreteFactory2::~ConcreteFactory2()
            {

            }

            AbstractProductA* ConcreteFactory2::CreateProductA()
            {
             return new ProductA2();
            }

            AbstractProductB* ConcreteFactory2::CreateProductB()
            {
             return new ProductB2();
            }

            代碼片斷5main.cpp
            //main.cpp

            #include "AbstractFactory.h"

            #include <iostream>
            using namespace std;

            int main(int argc,char* argv[])
            {
             AbstractFactory* cf1 = new ConcreteFactory1();

             cf1->CreateProductA();
             cf1->CreateProductB();

             AbstractFactory* cf2 = new ConcreteFactory2();
             cf2->CreateProductA();
             cf2->CreateProductB();

             return 0;
            }

                   AbstractFactory模式的實現代碼很簡單,在測試程序中可以看到,當我們要創建一組對象(ProductA1,ProductA2)的時候我們只用維護一個創建對象(ConcreteFactory1),大大簡化了維護的成本和工作。

            2.4 討論

                   AbstractFactory模式和Factory模式的區別是初學(使用)設計模式時候的一個容易引起困惑的地方。實際上,AbstractFactory模式是為創建一組(有多類)相關或依賴的對象提供創建接口,而Factory模式正如我在相應的文檔中分析的是為一類對象提供創建接口或延遲對象的創建到子類中實現。并且可以看到,AbstractFactory模式通常都是使用Factory模式實現(ConcreteFactory1)。


            posted @ 2008-10-22 10:44 micheal's tech 閱讀(384) | 評論 (0)編輯 收藏

            0 引言

            0.1 目的

                   本文檔給出設計模式之——Factory模式的簡化詮釋,并給出其C++實現。

            0.2 說明

            Project

            Design Pattern Explanation(By K_Eckel)

            Authorization

            Free Distributed but Ownership Reserved

            Date

            Test Bed

            MS Visual C++ 6.0

            0.3 參考

                   在本文檔的寫作中,參考了以下的資源,在此列出表示感謝:

            u       書籍

            [GoF 2000]:GoF,Design Patterns-Elements of Reusable Object-Oriented Software Addison-Wesley 2000/9.

                    [Martine 2003]:Robert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.

            u       網頁

            0.4 聯系作者

            Author

            K_Eckel

            State

            Candidate for Master’s Degree School of

            E_mail

            frwei@whu.edu.cn  

             

            2 Factory模式

            2.1 問題

                   在面向對象系統設計中經常可以遇到以下的兩類問題:

                   1)為了提高內聚(Cohesion)和松耦合(Coupling),我們經常會抽象出一些類的公共接口以形成抽象基類或者接口。這樣我們可以通過聲明一個指向基類的指針來指向實際的子類實現,達到了多態的目的。這里很容易出現的一個問題n多的子類繼承自抽象基類,我們不得不在每次要用到子類的地方就編寫諸如new ×××;的代碼。這里帶來兩個問題1)客戶程序員必須知道實際子類的名稱(當系統復雜后,命名將是一個很不好處理的問題,為了處理可能的名字沖突,有的命名可能并不是具有很好的可讀性和可記憶性,就姑且不論不同程序員千奇百怪的個人偏好了。),2)程序的擴展性和維護變得越來越困難。

                   2)還有一種情況就是在父類中并不知道具體要實例化哪一個具體的子類。這里的意思為:假設我們在類A中要使用到類B,B是一個抽象父類,在A中并不知道具體要實例化那一個B的子類,但是在類A的子類D中是可以知道的。在A中我們沒有辦法直接使用類似于new ×××的語句,因為根本就不知道×××是什么。

                   以上兩個問題也就引出了Factory模式的兩個最重要的功能:

                   1)定義創建對象的接口,封裝了對象的創建;

                   2)使得具體化類的工作延遲到了子類中。

            2.2 模式選擇

                   我們通常使用Factory模式來解決上面給出的兩個問題。在第一個問題中,我們經常就是聲明一個創建對象的接口,并封裝了對象的創建過程。Factory這里類似于一個真正意義上的工廠(生產對象)。在第二個問題中,我們需要提供一個對象創建對象的接口,并在子類中提供其具體實現(因為只有在子類中可以決定到底實例化哪一個類)。第一中情況的Factory的結構示意圖為:


            1Factory模式結構示意圖1

                   1所以的Factory模式經常在系統開發中用到,但是這并不是Factory模式的最大威力所在(因為這可以通過其他方式解決這個問題)。Factory模式不單是提供了創建對象的接口,其最重要的是延遲了子類的實例化(第二個問題),以下是這種情況的一個Factory的結構示意圖:


            2Factory模式結構示意圖1

                   2中關鍵中Factory模式的應用并不是只是為了封裝對象的創建,而是要把對象的創建放到子類中實現:Factory中只是提供了對象創建的接口,其實現將放在Factory的子類ConcreteFactory中進行。這是圖2和圖1的區別所在。

            2.3 實現

             代碼片斷1:Product.h
            //Product.h

            #ifndef _PRODUCT_H_
            #define _PRODUCT_H_

            class Product
            {
            public:
            virtual ~Product() =0;

            protected:
            Product();

            private:

            };

            class ConcreteProduct:publicProduct
            {
            public:
            ~ConcreteProduct();

            ConcreteProduct();

            protected:

            private:

            };

            #endif //~_PRODUCT_H_

            代碼片斷2:Product.cpp
            //Product.cpp

            #include "Product.h"

            #include<iostream>
            using namespace std;

            Product::Product()
            {

            }

            Product::~Product()
            {

            }

            ConcreteProduct::ConcreteProduct()
            {
            cout<<"ConcreteProduct...."<<endl;
            }

            ConcreteProduct::~ConcreteProduct()
            {

            }

            代碼片斷3:Factory.h
            //Factory.h

            #ifndef _FACTORY_H_
            #define _FACTORY_H_

            class Product;

            class Factory
            {
            public:
             virtual ~Factory() = 0;

             virtual Product* CreateProduct() = 0;

            protected:
             Factory();

            private:

            };

            class ConcreteFactory:public Factory
            {
            public:

             ~ConcreteFactory();

             ConcreteFactory();

             Product* CreateProduct();

            protected:

            private:

            };

            #endif //~_FACTORY_H_

            代碼片斷4:Factory.cpp
            //Factory.cpp

            #include "Factory.h"
            #include "Product.h"

            #include <iostream>
            using namespace std;

            Factory::Factory()
            {

            }

            Factory::~Factory()
            {

            }

            ConcreteFactory::ConcreteFactory()
            {
             cout<<"ConcreteFactory....."<<endl;
            }

            ConcreteFactory::~ConcreteFactory()
            {

            }

            Product* ConcreteFactory::CreateProduct()
            {
             return new ConcreteProduct();
            }

            代碼片斷5:main.cpp
            //main.cpp

            #include "Factory.h"
            #include "Product.h"

            #include <iostream>
            using namespace std;

            int main(int argc,char* argv[])
            {
             Factory* fac = new ConcreteFactory();

             Product* p = fac->CreateProduct();

             return 0;
            }


            2.4 討論

                 Factory 模式在實際開發中應用非常廣泛,面向對象的系統經常面臨著對象創建問題:要創建的類實在是太多了。而Factory提供的創建對象的接口封裝(第一個功 能),以及其將類的實例化推遲到子類(第二個功能)都部分地解決了實際問題。一個簡單的例子就是筆者開開發Visual CMCS系統的語義分析過程中,由于要為文法中的每個非終結符構造一個類處理,因此這個過程中對象的創建非常多,采用Factory模式后系統可讀性性和 維護都變得elegant許多。
                 Factory模式也帶來至少以下兩個問題:
                 1)如果為每一個具體的ConcreteProduct類的實例化提供一個函數體,那么我們可能不得不在系統中添加了一個方法來處理這個新建的 ConcreteProduct,這樣Factory的接口永遠就不肯能封閉(Close)。當然我們可以通過創建一個Factory的子類來通過多態實 現這一點,但是這也是以新建一個類作為代價的。
                 2)在實現中我們可以通過參數化工廠方法,即給FactoryMethod()傳遞一個參數用以決定是創建具體哪一個具體的Product(實際上筆者在 Visual CMCS中也正是這樣做的)。當然也可以通過模板化避免1)中的子類創建子類,其方法就是將具體Product類作為模板參數,實現起來也很簡單。
                 可以看出,Factory模式對于對象的創建給予開發人員提供了很好的實現策略,但是Factory模式僅僅局限于一類類(就是說Product是一類, 有一個共同的基類),如果我們要為不同類的類提供一個對象創建的接口,那就要用Abstract Factory了。


            posted @ 2008-10-21 16:45 micheal's tech 閱讀(632) | 評論 (0)編輯 收藏

            class Decorator:public Beverage
            {
                public:
                    Decorator(Beverage * com);
                    virtual ~Decorator();
                    virtual string get_descrption();
                protected:
                    Beverage * component;


            };

            而MilkDecorator繼承了Decorator,如果component 為私有的則MilkDecorator便不能訪問。

            如果milkDecorator 設計成這樣就不會違反了封裝的原則。
            基本上只有一個區別,就是protect成員能被派生類訪問!而派生類對private沒有特殊訪問權!

            posted @ 2008-10-16 17:04 micheal's tech 閱讀(247) | 評論 (0)編輯 收藏

            http://book.csdn.net/bookfiles/575/10057518902.shtml

            虛線箭頭表示“依賴關系”,依賴有“使用”的語義,比如患者與醫生的關系。
            實線箭頭表示“帶了導航行的關聯關系”,從一個類到另一類。
            使用實線箭頭時通常會帶上“多重性”的表達方式。如:一對多,一對一,多對多等等

            常見的關系有:一般化關系(Generalization),關聯關系(Association),聚合關系(Aggregation),合成關系(Composition),依賴關系(Dependency)。

                  其中,聚合關系(Aggregation),合成關系(Composition)屬于關聯關系(Association)。

                  一般關系表現為繼承或實現關系(is a),關聯關系表現為變量(has a ),依賴關系表現為函數中的參數(use a)。

                  一般化關系:表示為類與類之間的繼承關系,接口與接口之間的繼承,類對接口的實現關系。
                  表示方法: 用一個空心箭頭+實線,箭頭指向父類。或空心箭頭+虛線,如果父類是接口。

                  關聯關系:類與類之間的聯接,它使一個類知道另一個類的屬性和方法。
                  表示方法:用 實線+箭頭, 箭頭指向被使用的類。

                  聚合關系:是關聯關系的一種,是強的關聯關系。聚合關系是整體和個體的關系。關聯關系的兩個類處于同一層次上,啊聚合關系兩個類處于不同的層次,一個是整體,一個是部分。
                  表示方法:空心菱形+實線+箭頭,箭頭指向部分。

                  合成關系:是關聯關系的一種,是比聚合關系強的關系。它要求普通的聚合關系中代表整體的對象負責代表部分的對象的生命周期,合成關系不能共享。
                  表示方法:實心菱形+實線+箭頭,

                  依賴關系:是類與類之間的連接,表示一個類依賴于另一個類的定義。例如如果A依賴于B,則B體現為局部變量,方法的參數、或靜態方法的調用。
                  表示方法:虛線+箭頭


            圖一:

            uploads/200706/04_211918_1121523.jpg


            此實線箭頭表示, 繼承, 從一個非接口類的繼承.

            圖二:
            uploads/200706/04_212112_1121525gl.jpg


            那條連線表示雙向關聯:
            看左邊, Flight扮演assignedFights角色, 有0到1個Plane跟他關聯(一個航班要么取消了沒有飛機,要么只能對應一架飛機)
            看右邊, Plane扮演著assignedPlane角色, 有0到多個Flight跟他關聯(一個飛機可以參與多個航班, 也可以停在倉庫里面爛掉)

            圖三:
            uploads/200706/04_213002_1121526dxgl.jpg


            那條連線表示單向關聯:
            基本的意義跟上面的是一樣的, 唯一不同的是, 右邊的類對左邊的類是一無所知的.

            圖四:
            uploads/200706/04_213232_1121527rjb.jpg


            那個大的包圍的框叫軟件包, 名字為Account, 就一些可以歸類的類包裝起來.

            圖五:
            uploads/200706/04_213441_1121529xjc.gif


            如此虛線的箭頭表示實現一個接口.

            圖六:
            uploads/200706/04_213626_11215210gll.jpg


            水平的連線還是表示上面所說的關聯, 但從關聯連線中引伸出來的虛線, 這意味當Flight類的一個實例關聯到 FrequentFlyer 類的一個實例時,將會產生 MileageCredit 類的一個實例.

            圖七:
            uploads/200706/04_213911_11215211jbjh.jpg


            帶菱形的箭頭表示基本聚合, 由上圖知道, Wheel類扮演wheels角色, 聚合4個到Car對象里面去,
            空心的菱形表示Wheel對象并不隨Car的創建而創建,銷毀而銷毀.

            圖八:
            uploads/200706/04_214248_11215212zhjh.jpg


            意義和上面類似, 唯一不同的是, 實心菱形表示Department對象隨Company對象的創建而創建,銷毀而銷毀.

            圖九:
            uploads/200706/04_214435_11215213fs.gif


            表示反射關聯, 顯示一個Employee類如何通過manager / manages角色與它本身相關。當一個類關聯到它本身時,這并不意味著類的實例與它本身相關,而是類的一個實例與類的另一個實例相關。

            posted @ 2008-10-13 14:53 micheal's tech 閱讀(1779) | 評論 (0)編輯 收藏

            0 引言

            0.1 目的

                   本文檔給出設計模式之——Observer模式的簡化詮釋,并給出其C++實現。

            0.2 說明

            Project

            Design Pattern Explanation(By K_Eckel)

            Authorization

            Free Distributed but Ownership Reserved

            Date

            Test Bed

            MS Visual C++ 6.0

            0.3 參考

                   在本文檔的寫作中,參考了以下的資源,在此列出表示感謝:

            u       書籍

            [GoF 2000]:GoF,Design Patterns-Elements of Reusable Object-Oriented Software

            Addison-Wesley 2000/9.

                    [Martine 2003]:Robert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.

            0.4 聯系作者

            Author

            K_Eckel

            State

            Candidate for Master’s Degree School of

            E_mail

            frwei@whu.edu.cn  

            2 Observer模式

            2.1 問題

                   Observer模式應該可以說是應用最多、影響最廣的模式之一,因為Observer的一個實例Model/View/Control(MVC)結構在系統開發架構設計中有著很重要的地位和意義,MVC實現了業務邏輯和表示層的解耦。個人也認為Observer模式是軟件開發過程中必須要掌握和使用的模式之一。在MFC中,Doc/View(文檔視圖結構)提供了實現MVC的框架結構(有一個從設計模式(Observer模式)的角度分析分析Doc/View的文章正在進一步的撰寫當中,遺憾的是時間:))。在Java陣容中,Struts則提供和MFC中Doc/View結構類似的實現MVC的框架。另外Java語言本身就提供了Observer模式的實現接口,這將在討論中給出。

                   當然,MVC只是Observer模式的一個實例。Observer模式要解決的問題為:建立一個一(Subject)對多(Observer) 的依賴關系,并且做到當“一”變化的時候,依賴這個“一”的多也能夠同步改變。最常見的一個例子就是:對同一組數據進行統計分析時候,我們希望能夠提供多 種形式的表示(例如以表格進行統計顯示、柱狀圖統計顯示、百分比統計顯示等)。這些表示都依賴于同一組數據,我們當然需要當數據改變的時候,所有的統計的 顯示都能夠同時改變。Observer模式就是解決了這一個問題。

            2.2 模式選擇

                   Observer模式典型的結構圖為:


            2-1Observer Pattern結構圖

                   這里的目標Subject提供依賴于它的觀察者Observer的注冊(Attach)和注銷(Detach)操作,并且提供了使得依賴于它的所有觀察者同步的操作(Notify)。觀察者Observer則提供一個Update操作,注意這里的ObserverUpdate操作并不在Observer改變了Subject目標狀態的時候就對自己進行更新,這個更新操作要延遲到Subject對象發出Notify通知所有Observer進行修改(調用Update)。

            2.3 實現

                   Observer模式的實現有些特點,這里為了方便初學者的學習和參考,將給出完整的實現代碼(所有代碼采用C++實現,并在VC 6.0下測試運行)。

            代碼片斷1Subject.h
            //Subject.h

            #ifndef _SUBJECT_H_
            #define _SUBJECT_H_

            #include <list>
            #include <string>
            using namespace std;

            typedef string State;

            class Observer;

            class Subject
            {
            public:
             virtual ~Subject();

             virtual void Attach(Observer* obv);

             virtual void Detach(Observer* obv);

             virtual void Notify();

             virtual void SetState(const State& st) = 0;

             virtual State GetState() = 0;

            protected:
             Subject();

            private:
             list<Observer* >* _obvs;

            };

            class ConcreteSubject:public Subject
            {
            public:
             ConcreteSubject();

             ~ConcreteSubject();

             State GetState();

             void SetState(const State& st);

            protected:

            private:
             State _st;

            };

            #endif //~_SUBJECT_H_

            代碼片斷2Subject.cpp
            //Subject.cpp

            #include "Subject.h"
            #include "Observer.h"

            #include <iostream>
            #include <list>
            using namespace std;

            typedef string state;

            Subject::Subject()
            {
             //****在模板的使用之前一定要new,創建
             _obvs = new list<Observer*>;

            }

            Subject::~Subject()
            {
             
            }

            void Subject::Attach(Observer* obv)
            {
             
             _obvs->push_front(obv);
            }

            void Subject::Detach(Observer* obv)
            {
             if (obv != NULL)
              _obvs->remove(obv);
            }

            void Subject::Notify()
            {
             
             list<Observer*>::iterator it;

             it = _obvs->begin();

             for (;it != _obvs->end();it++)
             {
              //關于模板和iterator的用法

              (*it)->Update(this);
             }
            }

            ConcreteSubject::ConcreteSubject()
            {
             _st = '\0';
            }

            ConcreteSubject::~ConcreteSubject()
            {
             
            }


            State ConcreteSubject::GetState()
            {
             return _st;
            }

            void ConcreteSubject::SetState(const State& st)
            {
             _st = st;
            }

            代碼片斷3Observer.h
            //Observer.h

            #ifndef _OBSERVER_H_
            #define _OBSERVER_H_

            #include "Subject.h"

            #include <string>
            using namespace std;

            typedef string State;

            class Observer
            {
            public:
             virtual ~Observer();

             virtual void Update(Subject* sub) = 0;

             virtual void PrintInfo() = 0;

            protected:
             Observer();

             State _st;

            private:

            };

            class ConcreteObserverA:public Observer
            {
            public:
             virtual Subject* GetSubject();
             
             ConcreteObserverA(Subject* sub);

             virtual ~ConcreteObserverA();

             //傳入Subject作為參數,這樣可以讓一個View屬于多個的Subject
             void  Update(Subject* sub);

             void PrintInfo();

            protected:

            private:
             Subject* _sub;

            };

            class ConcreteObserverB:public Observer
            {
            public:
             virtual Subject* GetSubject();
             
             ConcreteObserverB(Subject* sub);

             virtual ~ConcreteObserverB();

             //傳入Subject作為參數,這樣可以讓一個View屬于多個的Subject
             void  Update(Subject* sub);

             void PrintInfo();

            protected:

            private:
             Subject* _sub;

            };

            #endif //~_OBSERVER_H_

            代碼片斷4Observer.cpp
            //Observer.cpp

            #include "Observer.h"
            #include "Subject.h"

            #include <iostream>
            #include <string>
            using namespace std;

            Observer::Observer()
            {
             _st = '\0';

            }

            Observer::~Observer()
            {

            }


            ConcreteObserverA::ConcreteObserverA(Subject* sub)
            {
             _sub = sub;

             _sub->Attach(this);
            }

            ConcreteObserverA::~ConcreteObserverA()
            {
             _sub->Detach(this);

             if (_sub != 0)
             {
              delete _sub;
             }
            }

            Subject* ConcreteObserverA::GetSubject()

             return _sub;
            }

            void ConcreteObserverA::PrintInfo()
            {
             cout<<"ConcreteObserverA observer.... "<<_sub->GetState()<<endl;
            }

            void ConcreteObserverA::Update(Subject* sub)
            {
             _st = sub->GetState();

             PrintInfo();
            }

            ConcreteObserverB::ConcreteObserverB(Subject* sub)
            {
             _sub = sub;

             _sub->Attach(this);
            }

            ConcreteObserverB::~ConcreteObserverB()
            {
             _sub->Detach(this);

             if (_sub != 0)
             {
              delete _sub;
             }
            }

            Subject* ConcreteObserverB::GetSubject()

             return _sub;
            }

            void ConcreteObserverB::PrintInfo()
            {
             cout<<"ConcreteObserverB observer.... "<<_sub->GetState()<<endl;
            }

            void ConcreteObserverB::Update(Subject* sub)
            {
             _st = sub->GetState();

             PrintInfo();
            }

            代碼片斷5main.cpp
            //main.cpp

            #include "Subject.h"
            #include "Observer.h"

            #include <iostream>
            using namespace std;

            int main(int argc,char* argv[])
            {
             ConcreteSubject* sub = new ConcreteSubject();

             Observer* o1 = new ConcreteObserverA(sub);

             Observer* o2 = new ConcreteObserverB(sub);

             sub->SetState("old");

             sub->Notify();

             sub->SetState("new"); //也可以由Observer調用

             sub->Notify();

             return 0;
            }

            在Observer模式的實現中,Subject維護一個list作為存儲其所有觀察者的容器。每當調用Notify操作就遍歷list中的Observer對象,并廣播通知改變狀態(調用Observer的Update操作)。目標的狀態state可以由Subject自己改變(示例),也可以由Observer的某個操作引起state的改變(可調用Subject的SetState操作)。Notify操作可以由Subject目標主動廣播(示例),也可以由Observer觀察者來調用(因為Observer維護一個指向Subject的指針)。

                   運行示例程序,可以看到當Subject處于狀態“old”時候,依賴于它的兩個觀察者都顯示“old”,當目標狀態改變為“new”的時候,依賴于它的兩個觀察者也都改變為“new”。

            2.4 討論

                   Observer是影響極為深遠的模式之一,也是在大型系統開發過程中要用到的模式之一。除了MFC、Struts提供了MVC的實現框架,在Java語言中還提供了專門的接口實現Observer模式:通過專門的類Observable及Observer接口來實現MVC編程模式,其UML圖可以表示為:

             


            Java中實現MVC的UML圖。

            這里的Observer就是觀察者,Observable則充當目標Subject的角色。

                   Observer模式也稱為發布-訂閱(publish-subscribe),目標就是通知的發布者,觀察者則是通知的訂閱者(接受通知)。


            posted @ 2008-10-10 11:04 micheal's tech 閱讀(420) | 評論 (0)編輯 收藏

            為什么C++編譯器不能支持對模板的分離式編譯
            劉未鵬(pongba) /文

            首先,C++標準中提到,一個編譯單元[translation unit]是指一個.cpp文件以及它所include的所有.h文件,.h文件里的代碼將會被擴展到包含它的.cpp文件里,然后編譯器編譯該.cpp 文件為一個.obj文件,后者擁有PE[Portable Executable,即windows可執行文件]文件格式,并且本身包含的就已經是二進制碼,但是,不一定能夠執行,因為并不保證其中一定有main 函數。當編譯器將一個工程里的所有.cpp文件以分離的方式編譯完畢后,再由連接器(linker)進行連接成為一個.exe文件。
            舉個例子:
            //---------------test.h-------------------//
            void f();//這里聲明一個函數f
            //---------------test.cpp--------------//
            #i nclude”test.h”
            void f()
            {
            …//do something
            } //這里實現出test.h中聲明的f函數
            //---------------main.cpp--------------//
            #i nclude”test.h”
            int main()
            {
            f(); //調用f,f具有外部連接類型
            }
            在 這個例子中,test. cpp和main.cpp各被編譯成為不同的.obj文件[姑且命名為test.obj和main.obj],在main.cpp中,調用了f函數,然而 當編譯器編譯main.cpp時,它所僅僅知道的只是main.cpp中所包含的test.h文件中的一個關于void f();的聲明,所以,編譯器將這里的f看作外部連接類型,即認為它的函數實現代碼在另一個.obj文件中,本例也就是test.obj,也就是 說,main.obj中實際沒有關于f函數的哪怕一行二進制代碼,而這些代碼實際存在于test.cpp所編譯成的test.obj中。在 main.obj中對f的調用只會生成一行call指令,像這樣:
            call f [C++中這個名字當然是經過mangling[處理]過的]
            在 編譯時,這個call指令顯然是錯誤的,因為main.obj中并無一行f的實現代碼。那怎么辦呢?這就是連接器的任務,連接器負責在其它的.obj中 [本例為test.obj]尋找f的實現代碼,找到以后將call f這個指令的調用地址換成實際的f的函數進入點地址。需要注意的是:連接器實際上將工程里的.obj“連接”成了一個.exe文件,而它最關鍵的任務就是 上面說的,尋找一個外部連接符號在另一個.obj中的地址,然后替換原來的“虛假”地址。
            這個過程如果說的更深入就是:
            call f這行指令其實并不是這樣的,它實際上是所謂的stub,也就是一個
            jmp 0x23423[這個地址可能是任意的,然而關鍵是這個地址上有一行指令來進行真正的call f動作。也就是說,這個.obj文件里面所有對f的調用都jmp向同一個地址,在后者那兒才真正”call”f。這樣做的好處就是連接器修改地址時只要對 后者的call XXX地址作改動就行了。但是,連接器是如何找到f的實際地址的呢[在本例中這處于test.obj中],因為.obj于.exe的格式都是一樣的,在這 樣的文件中有一個符號導入表和符號導出表[import table和export table]其中將所有符號和它們的地址關聯起來。這樣連接器只要在test.obj的符號導出表中尋找符號f[當然C++對f作了mangling]的 地址就行了,然后作一些偏移量處理后[因為是將兩個.obj文件合并,當然地址會有一定的偏移,這個連接器清楚]寫入main.obj中的符號導入表中f 所占有的那一項。
            這就是大概的過程。其中關鍵就是:
            編譯main.cpp時,編譯器不知道f的實現,所有當碰到對它的調用時只是給出一個指示,指示連接器應該為它尋找f的實現體。這也就是說main.obj中沒有關于f的任何一行二進制代碼。
            編譯test.cpp時,編譯器找到了f的實現。于是乎f的實現[二進制代碼]出現在test.obj里。
            連接時,連接器在test.obj中找到f的實現代碼[二進制]的地址[通過符號導出表]。然后將main.obj中懸而未決的call XXX地址改成f實際的地址。
            完成。

            然而,對于模板,你知道,模板函數的代碼其實并不能直接編譯成二進制代碼,其中要有一個“具現化”的過程。舉個例子:
            //----------main.cpp------//
            template<class T>
            void f(T t)
            {}
            int main()
            {
            …//do something
            f(10); //call f<int> 編譯器在這里決定給f一個f<int>的具現體
            …//do other thing
            }
            也就是說,如果你在main.cpp文件中沒有調用過f,f也就得不到具現,從而main.obj中也就沒有關于f的任意一行二進制代碼!!如果你這樣調用了:
            f(10); //f<int>得以具現化出來
            f(10.0); //f<double>得以具現化出來
            這樣main.obj中也就有了f<int>,f<double>兩個函數的二進制代碼段。以此類推。
            然而具現化要求編譯器知道模板的定義,不是嗎?
            看下面的例子:[將模板和它的實現分離]
            //-------------test.h----------------//
            template<class T>
            class A
            {
            public:
            void f(); //這里只是個聲明
            };
            //---------------test.cpp-------------//
            #i nclude”test.h”
            template<class T>
            void A<T>::f() //模板的實現,但注意:不是具現
            {
            …//do something
            }
            //---------------main.cpp---------------//
            #i nclude”test.h”
            int main()
            {
            A<int> a;
            a. f(); //編譯器在這里并不知道A<int>::f的定義,因為它不在test.h里面
            //于是編譯器只好寄希望于連接器,希望它能夠在其他.obj里面找到
            //A<int>::f的實現體,在本例中就是test.obj,然而,后者中真有A<int>::f的
            //二進制代碼嗎?NO!!!因為C++標準明確表示,當一個模板不被用到的時
            //侯它就不該被具現出來,test.cpp中用到了A<int>::f了嗎?沒有!!所以實
            //際上test.cpp編譯出來的test.obj文件中關于A::f的一行二進制代碼也沒有
            //于是連接器就傻眼了,只好給出一個連接錯誤
            // 但是,如果在test.cpp中寫一個函數,其中調用A<int>::f,則編譯器會將其//具現出來,因為在這個點上[test.cpp 中],編譯器知道模板的定義,所以能//夠具現化,于是,test.obj的符號導出表中就有了A<int>::f這個符號的地
            //址,于是連接器就能夠完成任務。
            }

            關鍵是:在分離式編譯的環境下,編譯器編譯某一個.cpp文件時并不知道另一個.cpp文件的存在,也不會去查找[當遇到未決符號時它會寄希望于連 接器]。這種模式在沒有模板的情況下運行良好,但遇到模板時就傻眼了,因為模板僅在需要的時候才會具現化出來,所以,當編譯器只看到模板的聲明時,它不能 具現化該模板,只能創建一個具有外部連接的符號并期待連接器能夠將符號的地址決議出來。然而當實現該模板的.cpp文件中沒有用到模板的具現體時,編譯器 懶得去具現,所以,整個工程的.obj中就找不到一行模板具現體的二進制代碼,于是連接器也黔

            /////////////////////////////////
            http://dev.csdn.net/develop/article/19/19587.shtm
             C++模板代碼的組織方式 ——包含模式(Inclusion Model)     選擇自 sam1111 的 Blog 
            關鍵字   Template Inclusion Model
            出處   C++ Template: The Complete Guide


            說明:本文譯自《C++ Template: The Complete Guide》一書的第6章中的部分內容。最近看到C++論壇上常有關于模板的包含模式的帖子,聯想到自己初學模板時,也為類似的問題困惑過,因此翻譯此文,希望對初學者有所幫助。

            模板代碼有幾種不同的組織方式,本文介紹其中最流行的一種方式:包含模式。

            鏈接錯誤

            大多數C/C++程序員向下面這樣組織他們的非模板代碼:

                     ·類和其他類型全部放在頭文件中,這些頭文件具有.hpp(或者.H, .h, .hh, .hxx)擴展名。

                     ·對于全局變量和(非內聯)函數,只有聲明放在頭文件中,而定義放在點C文件中,這些文件具有.cpp(或者.C, .c, .cc, .cxx)擴展名。
             

            這種組織方式工作的很好:它使得在編程時可以方便地訪問所需的類型定義,并且避免了來自鏈接器的“變量或函數重復定義”的錯誤。
             

            由于以上組織方式約定的影響,模板編程新手往往會犯一個同樣的錯誤。下面這一小段程序反映了這種錯誤。就像對待“普通代碼”那樣,我們在頭文件中定義模板:
             

            // basics/myfirst.hpp 

            #ifndef MYFIRST_HPP
            #define MYFIRST_HPP 

            // declaration of template

            template <typename T>

            void print_typeof (T const&);

            #endif // MYFIRST_HPP

             

            print_typeof()聲明了一個簡單的輔助函數用來打印一些類型信息。函數的定義放在點C文件中:

            // basics/myfirst.cpp

            #i nclude <iostream>

            #i nclude <typeinfo>

            #i nclude "myfirst.hpp"
             

            // implementation/definition of template

            template <typename T>
            void print_typeof (T const& x)
            {

                std::cout << typeid(x).name() << std::endl;

            }

             

            這個例子使用typeid操作符來打印一個字符串,這個字符串描述了傳入的參數的類型信息。

            最后,我們在另外一個點C文件中使用我們的模板,在這個文件中模板聲明被#i nclude:

            // basics/myfirstmain.cpp 

            #i nclude "myfirst.hpp" 

            // use of the template

            int main()
            {

                double ice = 3.0;
                print_typeof(ice);  // call function template for type double

            }

             
            大部分C++編譯器(Compiler)很可能會接受這個程序,沒有任何問題,但是鏈接器(Linker)大概會報告一個錯誤,指出缺少函數print_typeof()的定義。

            這個錯誤的原因在于,模板函數print_typeof()的定義還沒有被具現化(instantiate)。為了具現化一個模板,編譯器必須知道 哪一個定義應該被具現化,以及使用什么樣的模板參數來具現化。不幸的是,在前面的例子中,這兩組信息存在于分開編譯的不同文件中。因此,當我們的編譯器看 到對print_typeof()的調用,但是沒有看到此函數為double類型具現化的定義時,它只是假設這樣的定義在別處提供,并且創建一個那個定義 的引用(鏈接器使用此引用解析)。另一方面,當編譯器處理myfirst.cpp時,該文件并沒有任何指示表明它必須為它所包含的特殊參數具現化模板定 義。

            頭文件中的模板

            解決上面這個問題的通用解法是,采用與我們使用宏或者內聯函數相同的方法:我們將模板的定義包含進聲明模板的頭文件中。對于我們的例子,我們可以通 過將#i nclude "myfirst.cpp"添加到myfirst.hpp文件尾部,或者在每一個使用我們的模板的點C文件中包含myfirst.cpp文件,來達到目 的。當然,還有第三種方法,就是刪掉myfirst.cpp文件,并重寫myfirst.hpp文件,使它包含所有的模板聲明與定義:


            // basics/myfirst2.hpp

            #ifndef MYFIRST_HPP
            #define MYFIRST_HPP 

            #i nclude <iostream>
            #i nclude <typeinfo>
             

            // declaration of template
            template <typename T>
            void print_typeof (T const&); 

            // implementation/definition of template
            template <typename T>
            void print_typeof (T const& x)
            {

                std::cout << typeid(x).name() << std::endl;

            }

            #endif // MYFIRST_HPP

             

            這種組織模板代碼的方式就稱作包含模式。經過這樣的調整,你會發現我們的程序已經能夠正確編譯、鏈接、執行了。

            從這個方法中我們可以得到一些觀察結果。最值得注意的一點是,這個方法在相當程度上增加了包含myfirst.hpp的開銷。在這個例子中,這種開 銷并不是由模板定義自身的尺寸引起的,而是由這樣一個事實引起的,即我們必須包含我們的模板用到的頭文件,在這個例子中 是<iostream>和<typeinfo>。你會發現這最終導致了成千上萬行的代碼,因為諸 如<iostream>這樣的頭文件也包含了和我們類似的模板定義。

            這在實踐中確實是一個問題,因為它增加了編譯器在編譯一個實際程序時所需的時間。我們因此會在以后的章節中驗證其他一些可能的方法來解決這個問題。但無論如何,現實世界中的程序花一小時來編譯鏈接已經是快的了(我們曾經遇到過花費數天時間來從源碼編譯的程序)。

            拋開編譯時間不談,我們強烈建議如果可能盡量按照包含模式組織模板代碼。

            另一個觀察結果是,非內聯模板函數與內聯函數和宏的最重要的不同在于:它并不會在調用端展開。相反,當模板函數被具現化時,會產生此函數的一個新的 拷貝。由于這是一個自動的過程,編譯器也許會在不同的文件中產生兩個相同的拷貝,從而引起鏈接器報告一個錯誤。理論上,我們并不關心這一點:這是編譯器設 計者應當關心的事情。實際上,大多數時候一切都運轉正常,我們根本就不用處理這種狀況。然而,對于那些需要創建自己的庫的大型項目,這個問題偶爾會顯現出 來。
             

            最后,需要指出的是,在我們的例子中,應用于普通模板函數的方法同樣適用于模板類的成員函數和靜態數據成員,以及模板成員函數。



            posted @ 2008-10-09 17:23 micheal's tech 閱讀(1889) | 評論 (0)編輯 收藏

            #include  <iostream>
            using namespace std;
            #include <assert.h>
            int MergeSort(int A[],int p,int q,int r)
            {
                int n1 = q-p+2;
                int n2 = r-q+1;
                int *parray1 = new int[n1];
                int *parray2 = new int[n2];
                assert(parray1);
                assert(parray2);
                for(int i = 0;i<n1-1;i++)
                {
                    parray1[i] = A[p+i];
                }
                parray1[n1-1] = 0xffff;
                for(int i = 0;i<n2-1;i++)
                {
                    parray2[i] = A[q+i+1];
                }
                parray2[n2-1] = 0xffff;
                for(int i=0,j=0,k=p;k<=r;k++)
                {
                    if(parray1[i]>parray2[j])
                    {
                        A[k] = parray2[j];
                        j++;
                    }
                    else
                    {
                        A[k] = parray1[i];
                        i++;
                    }
                }

                delete []parray1;
                delete []parray2;


            }
            int Merge(int A[],int p,int r)
            {
                if(p<r)
                {
                   int q = (p+r)/2;
                   Merge(A,p,q);
                   Merge(A,q+1,r);
                   MergeSort(A,p,q,r);
                }
            }
            int main()
            {
                int a[]={5,2,4,7,1,3,2,6};
                Merge(a,0,sizeof(a)/sizeof(int)-1);
                for(int i=0;i<8;i++)
                {
                    cout<<a[i]<<" ";
                }
                cout<<endl;
            }



            posted @ 2008-10-08 15:01 micheal's tech 閱讀(855) | 評論 (0)編輯 收藏

            句柄類是存儲和管理基類指針的一個類
            需要句柄類的背景:
            1)在對安全要求很高的領域,即使核心實現已經封閉在庫中不可見,但頭文件中變量定義仍可能曝露一些內部信息
            2)在設計初期、實現部分會經常變動,甚至頭文件中變量定義也需要經常變動,因此在重編譯的時候頭文件也需要編譯,
            有時候導致編譯時間過長。
            句柄類就是為了解決這類問題
            // Handle.h
            class Implement; //這里是對真正實現功能的類的聲明

            class ImplementHandle //這是Implement類的句柄類
            {
            public:
            ImplementHandle():lpImplementInstance(NULL){lpImplementInstance = new Implement;}
            ~ImplementHandle(){ delete lpImplementInstance ;}
            public:
            // Interface_FOO() 是句柄類提供的接口,它的真正實現是在Implement類里面,而這個僅僅是接口"代理"
            RE_TYPE Interface_FOO()
            {
            return lpImplementInstance->FOO
            _
            Implementation(); //句柄代理調用實現真正的FOO接口
            }
            //還有其他的接口也一樣,均是用句柄類代理接口
            //....
            private:
            Implement * lpImplementInstance; //這個是句柄類唯一的數據成員(當然,并不一定),可以被句柄類很好地自動管理
            };




              被封裝在句柄類里面的真正實現(class Implement)將與用戶隔離開來,就是說,就算以后Implement 類的實現有所改變,
            只要它提供的接口不變,那么它的句柄類就不會改變,而包含句柄類的用戶,也不用做任何相應的改變(所有包含 “Handle.h”的模塊甚至不用從新編譯就可以正常更新至最新的Implement實現)。

            例如
            #include "Handle.h"
            Imlementhandle testhandle;
            testhandle->Interface_Foo();//調用接口即可。
            如果改動了Implent類的方法

              于此相反,如果直接用class Implement 的定義,那么只要Implement類的定義有所改變(不管是public 還是private 成員
            的更新),那么所有包含它的頭文件的模塊都要從新編譯一次。

            這其實就是接口與實現的分離,


            posted @ 2008-10-07 16:13 micheal's tech 閱讀(1165) | 評論 (0)編輯 收藏

            僅列出標題
            共8頁: 1 2 3 4 5 6 7 8 
            999久久久免费国产精品播放| 伊人久久五月天| 久久最近最新中文字幕大全 | 欧美大香线蕉线伊人久久| 91久久精品视频| 久久久无码精品亚洲日韩蜜臀浪潮 | 久久人人爽人人澡人人高潮AV| 久久精品国产亚洲av水果派| 狠狠色丁香久久婷婷综合_中 | 天天爽天天爽天天片a久久网| 亚洲国产成人久久综合区| 狠狠人妻久久久久久综合| 热99re久久国超精品首页| 久久久无码精品亚洲日韩京东传媒 | 精品久久人人妻人人做精品| 少妇久久久久久久久久| 日日噜噜夜夜狠狠久久丁香五月 | 久久亚洲私人国产精品vA| 欧美午夜A∨大片久久 | 韩国三级大全久久网站| 国产精品18久久久久久vr| 国产精品99久久不卡| 久久人人超碰精品CAOPOREN| 久久精品国产久精国产思思| 色狠狠久久综合网| 久久久久久极精品久久久| 亚洲国产成人久久综合区| 国内精品久久久久久久久| 久久精品国产一区| 97久久综合精品久久久综合| 国产亚洲欧美精品久久久| 久久久免费观成人影院 | 亚洲精品无码久久久久久| 亚洲人成无码网站久久99热国产| 91性高湖久久久久| 国产精品永久久久久久久久久| 狠狠干狠狠久久| 91久久精品电影| 色综合久久天天综线观看| 欧美性大战久久久久久| 一级a性色生活片久久无少妇一级婬片免费放 |