• <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
            本章首先簡(jiǎn)單介紹自定義內(nèi)存池性能優(yōu)化的原理,然后列舉軟件開發(fā)中常用的內(nèi)存池的不同類型,并給出具體實(shí)現(xiàn)的實(shí)例。

            引言

            本 書主要針對(duì)的是 C++ 程序的性能優(yōu)化,深入介紹 C++ 程序性能優(yōu)化的方法和實(shí)例。全書由 4 個(gè)篇組成,第 1 篇介紹 C++ 語言的對(duì)象模型,該篇是優(yōu)化 C++ 程序的基礎(chǔ);第 2 篇主要針對(duì)如何優(yōu)化 C++ 程序的內(nèi)存使用;第 3 篇介紹如何優(yōu)化程序的啟動(dòng)性能;第 4 篇介紹了三類性能優(yōu)化工具,即內(nèi)存分析工具、性能分析工具和 I/O 檢測(cè)工具,它們是測(cè)量程序性能的利器。

            在此我們推出了此書的第 26 章供大家在線瀏覽。更多推薦書籍請(qǐng)?jiān)L問 developerWorks 圖書頻道





            回頁首


            6.1 自定義內(nèi)存池性能優(yōu)化的原理


            書名:《C++應(yīng)用程序性能優(yōu)化》
            作者:馮宏華、徐瑩、程遠(yuǎn)、汪磊 等編著
            出版社:電子工業(yè)出版社
            出版日期:2007 年 03 月
            ISBN:978-7-121-03831-0
            購(gòu)買: 中國(guó)互動(dòng)出版網(wǎng)dearbook

            推薦章節(jié):


            如 前所述,讀者已經(jīng)了解到"堆"和"棧"的區(qū)別。而在編程實(shí)踐中,不可避免地要大量用到堆上的內(nèi)存。例如在程序中維護(hù)一個(gè)鏈表的數(shù)據(jù)結(jié)構(gòu)時(shí),每次新增或者刪 除一個(gè)鏈表的節(jié)點(diǎn),都需要從內(nèi)存堆上分配或者釋放一定的內(nèi)存;在維護(hù)一個(gè)動(dòng)態(tài)數(shù)組時(shí),如果動(dòng)態(tài)數(shù)組的大小不能滿足程序需要時(shí),也要在內(nèi)存堆上分配新的內(nèi)存 空間。

            6.1.1 默認(rèn)內(nèi)存管理函數(shù)的不足

            利用默認(rèn)的內(nèi)存管理函數(shù)new/delete或malloc/free在堆上分配和釋放內(nèi)存會(huì)有一些額外的開銷。

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

            默認(rèn)的內(nèi)存管理函數(shù)還考慮到多線程的應(yīng)用,需要在每次分配和釋放內(nèi)存時(shí)加鎖,同樣增加了開銷。

            可見,如果應(yīng)用程序頻繁地在堆上分配和釋放內(nèi)存,則會(huì)導(dǎo)致性能的損失。并且會(huì)使系統(tǒng)中出現(xiàn)大量的內(nèi)存碎片,降低內(nèi)存的利用率。

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

            6.1.2 內(nèi)存池的定義和分類

            自 定義內(nèi)存池的思想通過這個(gè)"池"字表露無疑,應(yīng)用程序可以通過系統(tǒng)的內(nèi)存分配調(diào)用預(yù)先一次性申請(qǐng)適當(dāng)大小的內(nèi)存作為一個(gè)內(nèi)存池,之后應(yīng)用程序自己對(duì)內(nèi)存的 分配和釋放則可以通過這個(gè)內(nèi)存池來完成。只有當(dāng)內(nèi)存池大小需要?jiǎng)討B(tài)擴(kuò)展時(shí),才需要再調(diào)用系統(tǒng)的內(nèi)存分配函數(shù),其他時(shí)間對(duì)內(nèi)存的一切操作都在應(yīng)用程序的掌控 之中。

            應(yīng)用程序自定義的內(nèi)存池根據(jù)不同的適用場(chǎng)景又有不同的類型。

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

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

            6.1.3 內(nèi)存池工作原理示例

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


            圖6-1 固定內(nèi)存池
            圖6-1  固定內(nèi)存池

            固定內(nèi)存池由一系列固定大小的內(nèi)存塊組成,每一個(gè)內(nèi)存塊又包含了固定數(shù)量和大小的內(nèi)存單元。

            如 圖6-1所示,該內(nèi)存池一共包含4個(gè)內(nèi)存塊。在內(nèi)存池初次生成時(shí),只向系統(tǒng)申請(qǐng)了一個(gè)內(nèi)存塊,返回的指針作為整個(gè)內(nèi)存池的頭指針。之后隨著應(yīng)用程序?qū)?nèi)存 的不斷需求,內(nèi)存池判斷需要?jiǎng)討B(tài)擴(kuò)大時(shí),才再次向系統(tǒng)申請(qǐng)新的內(nèi)存塊,并把所有這些內(nèi)存塊通過指針鏈接起來。對(duì)于操作系統(tǒng)來說,它已經(jīng)為該應(yīng)用程序分配了 4個(gè)等大小的內(nèi)存塊。由于是大小固定的,所以分配的速度比較快;而對(duì)于應(yīng)用程序來說,其內(nèi)存池開辟了一定大小,內(nèi)存池內(nèi)部卻還有剩余的空間。

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

            可見與系統(tǒng)管理內(nèi)存相比,內(nèi)存池的操作非常迅速,它在性能優(yōu)化方面的優(yōu)點(diǎn)主要如下。

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

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

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





            回頁首


            6.2 一個(gè)內(nèi)存池的實(shí)現(xiàn)實(shí)例

            本節(jié)分析在某個(gè)大型應(yīng)用程序?qū)嶋H應(yīng)用到的一個(gè)內(nèi)存池實(shí)現(xiàn),并詳細(xì)講解其使用方法與工作原理。這是一個(gè)應(yīng)用于單線程環(huán)境且分配單元大小固定的內(nèi)存池,一般用來為執(zhí)行時(shí)會(huì)動(dòng)態(tài)頻繁地創(chuàng)建且可能會(huì)被多次創(chuàng)建的類對(duì)象或者結(jié)構(gòu)體分配內(nèi)存。

            本節(jié)首先講解該內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)聲明及圖示,接著描述其原理及行為特征。然后逐一講解實(shí)現(xiàn)細(xì)節(jié),最后介紹如何在實(shí)際程序中應(yīng)用此內(nèi)存池,并與使用普通內(nèi)存函數(shù)申請(qǐng)內(nèi)存的程序性能作比較。

            6.2.1 內(nèi)部構(gòu)造

            內(nèi)存池類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為內(nèi)存池中附著在真正用來為內(nèi)存請(qǐng)求分配內(nèi)存的內(nèi)存塊頭部的結(jié)構(gòu)體,它描述了與之聯(lián)系的內(nèi)存塊的使用信息:


            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() {}
            };

            此內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)如圖6-2所示。


            圖6-2 內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)
            圖6-2  內(nèi)存池的數(shù)據(jù)結(jié)構(gòu)

            6.2.2 總體機(jī)制

            此內(nèi)存池的總體機(jī)制如下。

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

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

            (3)當(dāng)有新的內(nèi)存請(qǐng)求到來 時(shí),MemoryPool會(huì)通過pBlock遍歷MemoryBlock鏈表,直到找到某個(gè)MemoryBlock所在的內(nèi)存塊,其中還有自由分配單元 (通過檢測(cè)MemoryBlock結(jié)構(gòu)體的nFree成員是否大于0)。如果找到這樣的內(nèi)存塊,取得其MemoryBlock的nFirst值(此為該內(nèi) 存塊中第1個(gè)可供分配的自由單元的編號(hào))。然后根據(jù)這個(gè)編號(hào)定位到該自由分配單元的起始位置(因?yàn)樗蟹峙鋯卧笮」潭ǎ虼嗣總€(gè)分配單元的起始位置都可 以通過編號(hào)分配單元大小來偏移定位),這個(gè)位置就是用來滿足此次內(nèi)存申請(qǐng)請(qǐng)求的內(nèi)存的起始地址。但在返回這個(gè)地址前,需要首先將該位置開始的頭兩個(gè)字節(jié)的 值(這兩個(gè)字節(jié)值記錄其之后的下一個(gè)自由分配單元的編號(hào))賦給本內(nèi)存塊的MemoryBlock的nFirst成員。這樣下一次的請(qǐng)求就會(huì)用這個(gè)編號(hào)對(duì)應(yīng) 的內(nèi)存單元來滿足,同時(shí)將此內(nèi)存塊的MemoryBlock的nFree遞減1,然后才將剛才定位到的內(nèi)存單元的起始位置作為此次內(nèi)存請(qǐng)求的返回地址返回 給調(diào)用者。

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

            (5) 當(dāng)某個(gè)被分配的單元因?yàn)閐elete需要回收時(shí),該單元并不會(huì)返回給進(jìn)程堆,而是返回給MemoryPool。返回時(shí),MemoryPool能夠知道該單 元的起始地址。這時(shí),MemoryPool開始遍歷其所維護(hù)的內(nèi)存塊鏈表,判斷該單元的起始地址是否落在某個(gè)內(nèi)存塊的地址范圍內(nèi)。如果不在所有內(nèi)存地址范 圍內(nèi),則這個(gè)被回收的單元不屬于這個(gè)MemoryPool;如果在某個(gè)內(nèi)存塊的地址范圍內(nèi),那么它會(huì)將這個(gè)剛剛回收的分配單元加到這個(gè)內(nèi)存塊的 MemoryBlock所維護(hù)的自由分配單元鏈表的頭部,同時(shí)將其nFree值遞增1。回收后,考慮到資源的有效利用及后續(xù)操作的性能,內(nèi)存池的操作會(huì)繼 續(xù)判斷:如果此內(nèi)存塊的所有分配單元都是自由的,那么這個(gè)內(nèi)存塊就會(huì)從MemoryPool中被移出并作為一個(gè)整體返回給進(jìn)程堆;如果該內(nèi)存塊中還有非自 由分配單元,這時(shí)不能將此內(nèi)存塊返回給進(jìn)程堆。但是因?yàn)閯倓傆幸粋€(gè)分配單元返回給了這個(gè)內(nèi)存塊,即這個(gè)內(nèi)存塊有自由分配單元可供下次分配,因此它會(huì)被移到 MemoryPool維護(hù)的內(nèi)存塊的頭部。這樣下次的內(nèi)存請(qǐng)求到來,MemoryPool遍歷其內(nèi)存塊鏈表以尋找自由分配單元時(shí),第1次尋找就會(huì)找到這個(gè) 內(nèi)存塊。因?yàn)檫@個(gè)內(nèi)存塊確實(shí)有自由分配單元,這樣可以減少M(fèi)emoryPool的遍歷次數(shù)。

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

            6.2.3 細(xì)節(jié)剖析

            有了上述的總體印象后,本節(jié)來仔細(xì)剖析其實(shí)現(xiàn)細(xì)節(jié)。

            (1)MemoryPool的構(gòu)造如下:


            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創(chuàng)建時(shí),并沒有立刻創(chuàng)建真正用來滿足內(nèi)存申請(qǐng)的內(nèi)存塊,即內(nèi)存塊鏈表剛開始時(shí)為空。

            ②處和③處分別設(shè)置"第1次創(chuàng)建的內(nèi)存塊所包含的分配單元的個(gè)數(shù)",及"隨后創(chuàng)建的內(nèi)存塊所包含的分配單元的個(gè)數(shù)",這兩個(gè)值在MemoryPool創(chuàng)建時(shí)通過參數(shù)指定,其后在該MemoryPool對(duì)象生命周期中一直不變。

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

            (2)當(dāng)向MemoryPool提出內(nèi)存請(qǐng)求時(shí):


            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滿足內(nèi)存請(qǐng)求的步驟主要由四步組成。

            ① 處首先判斷內(nèi)存池當(dāng)前內(nèi)存塊鏈表是否為空,如果為空,則意味著這是第1次內(nèi)存申請(qǐng)請(qǐng)求。這時(shí),從進(jìn)程堆中申請(qǐng)一個(gè)分配單元個(gè)數(shù)為nInitSize的內(nèi)存 塊,并初始化該內(nèi)存塊(主要初始化MemoryBlock結(jié)構(gòu)體成員,以及創(chuàng)建初始的自由分配單元鏈表,下面會(huì)詳細(xì)分析其代碼)。如果該內(nèi)存塊申請(qǐng)成功, 并初始化完畢,返回第1個(gè)分配單元給調(diào)用函數(shù)。第1個(gè)分配單元以MemoryBlock結(jié)構(gòu)體內(nèi)的最后一個(gè)字節(jié)為起始地址。

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

            ③ 處檢查如果找到還有自由分配單元的內(nèi)存塊,則"定位"到該內(nèi)存塊現(xiàn)在可以用的自由分配單元處。"定位"以MemoryBlock結(jié)構(gòu)體內(nèi)的最后一個(gè)字節(jié)位 置aData為起始位置,以MemoryPool的nUnitSize為步長(zhǎng)來進(jìn)行。找到后,需要修改MemoryBlock的nFree信息(剩下來的 自由分配單元比原來減少了一個(gè)),以及修改此內(nèi)存塊的自由存儲(chǔ)單元鏈表的信息。在找到的內(nèi)存塊中,pMyBlock->nFirst為該內(nèi)存塊中自 由存儲(chǔ)單元鏈表的表頭,其下一個(gè)自由存儲(chǔ)單元的編號(hào)存放在pMyBlock->nFirst指示的自由存儲(chǔ)單元(亦即剛才定位到的自由存儲(chǔ)單元)的 頭兩個(gè)字節(jié)。通過剛才定位到的位置,取其頭兩個(gè)字節(jié)的值,賦給pMyBlock->nFirst,這就是此內(nèi)存塊的自由存儲(chǔ)單元鏈表的新的表頭,即 下一次分配出去的自由分配單元的編號(hào)(如果nFree大于零的話)。修改維護(hù)信息后,就可以將剛才定位到的自由分配單元的地址返回給此次申請(qǐng)的調(diào)用函數(shù)。 注意,因?yàn)檫@個(gè)分配單元已經(jīng)被分配,而內(nèi)存塊無須維護(hù)已分配的分配單元,因此該分配單元的頭兩個(gè)字節(jié)的信息已經(jīng)沒有用處。換個(gè)角度看,這個(gè)自由分配單元返 回給調(diào)用函數(shù)后,調(diào)用函數(shù)如何處置這塊內(nèi)存,內(nèi)存池?zé)o從知曉,也無須知曉。此分配單元在返回給調(diào)用函數(shù)時(shí),其內(nèi)容對(duì)于調(diào)用函數(shù)來說是無意義的。因此幾乎可 以肯定調(diào)用函數(shù)在用這個(gè)單元的內(nèi)存時(shí)會(huì)覆蓋其原來的內(nèi)容,即頭兩個(gè)字節(jié)的內(nèi)容也會(huì)被抹去。因此每個(gè)存儲(chǔ)單元并沒有因?yàn)樾枰溄佣攵嘤嗟木S護(hù)信息,而是 直接利用單元內(nèi)的頭兩個(gè)字節(jié),當(dāng)其分配后,頭兩個(gè)字節(jié)也可以被調(diào)用函數(shù)利用。而在自由狀態(tài)時(shí),則用來存放維護(hù)信息,即下一個(gè)自由分配單元的編號(hào),這是一個(gè) 有效利用內(nèi)存的好例子。

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

            可以用圖6-2的MemoryPool來展示MemoryPool::Alloc的過程。圖6-3是某個(gè)時(shí)刻MemoryPool的內(nèi)部狀態(tài)。


            圖6-3 某個(gè)時(shí)刻MemoryPool的內(nèi)部狀態(tài)
            圖6-3  某個(gè)時(shí)刻memorypool的內(nèi)部狀態(tài)

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


            圖6-4 MemoryPool的結(jié)構(gòu)
            圖6-4  memorypool的結(jié)構(gòu)

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

            (3)MemoryPool回收內(nèi)存時(shí):


            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
            {
            ……
            }
            }

            如前所述,回收分配單元時(shí),可能會(huì)將整個(gè)內(nèi)存塊返回給進(jìn)程堆,也可能將被回收分配單元所屬的內(nèi)存塊移至內(nèi)存池的內(nèi)存塊鏈表的頭部。這兩個(gè)操作都需要修改鏈表結(jié)構(gòu)。這時(shí)需要知道該內(nèi)存塊在鏈表中前一個(gè)位置的內(nèi)存塊。

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

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

            ③處用來修改該內(nèi)存塊的自由分配單元鏈表的信息,它將這個(gè)待回收分配單元的頭兩個(gè)字節(jié)的值指向該內(nèi)存塊原來的第一個(gè)可分配的自由分配單元的編號(hào)。

            ④處將pMyBlock的nFirst值改變?yōu)橹赶蜻@個(gè)待回收分配單元的編號(hào),其編號(hào)通過計(jì)算此單元的起始位置相對(duì)pMyBlock的aData位置的差值,然后除以步長(zhǎng)(nUnitSize)得到。

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

            ⑤處檢查當(dāng)回收完畢后,包含此回收單元的內(nèi)存塊的所有單元是否都處于自由狀態(tài),且此內(nèi)存是否處于內(nèi)存塊鏈表的頭部。如果是,將此內(nèi)存塊整個(gè)的返回給進(jìn)程堆,同時(shí)修改內(nèi)存塊鏈表結(jié)構(gòu)。

            注 意,這里在判斷一個(gè)內(nèi)存塊的所有單元是否都處于自由狀態(tài)時(shí),并沒有遍歷其所有單元,而是判斷nFree乘以nUnitSize是否等于nSize。 nSize是內(nèi)存塊中所有分配單元的大小,而不包括頭部MemoryBlock結(jié)構(gòu)體的大小。這里可以看到其用意,即用來快速檢查某個(gè)內(nèi)存塊中所有分配單 元是否全部處于自由狀態(tài)。因?yàn)橹恍杞Y(jié)合nFree和nUnitSize來計(jì)算得出結(jié)論,而無須遍歷和計(jì)算所有自由狀態(tài)的分配單元的個(gè)數(shù)。

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

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

            不 難發(fā)現(xiàn),這時(shí)nFirst的值由原來的0變?yōu)閙。即此內(nèi)存塊下一個(gè)被分配的單元是m編號(hào)的單元,而不是0編號(hào)的單元(最先分配的是最新回收的單元,從這一 點(diǎn)看,這個(gè)過程與棧的原理類似,即先進(jìn)后出。只不過這里的"進(jìn)"意味著"回收",而"出"則意味著"分配")。相應(yīng)地,m的"下一個(gè)自由單元"標(biāo)記為0, 即內(nèi)存塊原來的"下一個(gè)將被分配出去的單元",這也表明最近回收的分配單元被插到了內(nèi)存塊的"自由分配單元鏈表"的頭部。當(dāng)然,nFree遞增1。


            圖6-5 分配后的內(nèi)存池狀態(tài)
            圖6-5  分配后的內(nèi)存池狀態(tài)

            處理至⑥處之前,其狀態(tài)如圖6-6所示。


            圖6-6 處理至⑥處之前的內(nèi)存池狀態(tài)
            圖6-6  處理至⑥處之前的內(nèi)存池狀態(tài)

            這 里需要注意的是,雖然pFree被"回收",但是pFree仍然指向m編號(hào)的單元,這個(gè)單元在回收過程中,其頭兩個(gè)字節(jié)被覆寫,但其他部分的內(nèi)容并沒有改 變。而且從整個(gè)進(jìn)程的內(nèi)存使用角度來看,這個(gè)m編號(hào)的單元的狀態(tài)仍然是"有效的"。因?yàn)檫@里的"回收"只是回收給了內(nèi)存池,而并沒有回收給進(jìn)程堆,因此程 序仍然可以通過pFree訪問此單元。但是這是一個(gè)很危險(xiǎn)的操作,因?yàn)槭紫仍搯卧诨厥者^程中頭兩個(gè)字節(jié)已被覆寫,并且該單元可能很快就會(huì)被內(nèi)存池重新分 配。因此回收后通過pFree指針對(duì)這個(gè)單元的訪問都是錯(cuò)誤的,讀操作會(huì)讀到錯(cuò)誤的數(shù)據(jù),寫操作則可能會(huì)破壞程序中其他地方的數(shù)據(jù),因此需要格外小心。

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


            圖6-7 因回收引起的MemoryBlock移動(dòng)
            圖6-7  因回收引起的memoryblock移動(dòng)

            如果該內(nèi)存塊中省略號(hào)"……"表示的其他部分中全部都是自由分配單元,即nFree乘以nUnitSize等于nSize。因?yàn)榇藘?nèi)存塊不在鏈表頭,所以此時(shí)需要將此內(nèi)存塊整個(gè)回收給進(jìn)程堆,回收后內(nèi)存池的結(jié)構(gòu)如圖6-8所示。


            圖6-8 回收后內(nèi)存池的結(jié)構(gòu)
            圖6-8  回收后內(nèi)存池的結(jié)構(gòu)

            一個(gè)內(nèi)存塊在申請(qǐng)后會(huì)初始化,主要是為了建立最初的自由分配單元鏈表,下面是其詳細(xì)代碼:


            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編號(hào)單元。但是②處的循環(huán)中i卻是從1開始,然后在循環(huán)內(nèi)部的③處將pData的頭兩個(gè)字節(jié)值置為i。即0號(hào)單元的頭兩個(gè)字節(jié)值為1,1 號(hào)單元的頭兩個(gè)字節(jié)值為2,一直到(nTypes-2)號(hào)單元的頭兩個(gè)字節(jié)值為(nTypes-1)。這意味著內(nèi)存塊初始時(shí),其自由分配單元鏈表是從0號(hào) 開始。依次串聯(lián),一直到倒數(shù)第2個(gè)單元指向最后一個(gè)單元。

            還需要注意的是,在其初始化列表中,nFree初始 化為nTypes-1(而不是nTypes),nFirst初始化為1(而不是0)。這是因?yàn)榈?個(gè)單元,即0編號(hào)單元構(gòu)造完畢后,立刻會(huì)被分配。另外注 意到最后一個(gè)單元初始并沒有設(shè)置頭兩個(gè)字節(jié)的值,因?yàn)樵搯卧跏荚诒緝?nèi)存塊中并沒有下一個(gè)自由分配單元。但是從上面例子中可以看到,當(dāng)最后一個(gè)單元被分配 并回收后,其頭兩個(gè)字節(jié)會(huì)被設(shè)置。

            圖6-9所示為一個(gè)內(nèi)存塊初始化后的狀態(tài)。


            圖6-9 一個(gè)內(nèi)存塊初始化后的狀態(tài)
            圖6-9  一個(gè)內(nèi)存塊初始化后的狀態(tài)

            當(dāng)內(nèi)存池析構(gòu)時(shí),需要將內(nèi)存池的所有內(nèi)存塊返回給進(jìn)程堆:


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

            6.2.4 使用方法

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

            綜上 所述,當(dāng)需要提高某些關(guān)鍵類對(duì)象的申請(qǐng)/回收效率時(shí),可以考慮將該類所有生成對(duì)象所需的空間都從某個(gè)這樣的內(nèi)存池中開辟。在銷毀對(duì)象時(shí),只需要返回給該內(nèi) 存池。"一個(gè)類的所有對(duì)象都分配在同一個(gè)內(nèi)存池對(duì)象中"這一需求很自然的設(shè)計(jì)方法就是為這樣的類聲明一個(gè)靜態(tài)內(nèi)存池對(duì)象,同時(shí)為了讓其所有對(duì)象都從這個(gè)內(nèi) 存池中開辟內(nèi)存,而不是缺省的從進(jìn)程堆中獲得,需要為該類重載一個(gè)new運(yùn)算符。因?yàn)橄鄳?yīng)地,回收也是面向內(nèi)存池,而不是進(jìn)程的缺省堆,還需要重載一個(gè) delete運(yùn)算符。在new運(yùn)算符中用內(nèi)存池的Alloc函數(shù)滿足所有該類對(duì)象的內(nèi)存請(qǐng)求,而銷毀某對(duì)象則可以通過在delete運(yùn)算符中調(diào)用內(nèi)存池的 Free完成。

            6.2.5 性能比較

            為 了測(cè)試?yán)脙?nèi)存池后的效果,通過一個(gè)很小的測(cè)試程序可以發(fā)現(xiàn)采用內(nèi)存池機(jī)制后耗時(shí)為297 ms。而沒有采用內(nèi)存池機(jī)制則耗時(shí)625 ms,速度提高了52.48%。速度提高的原因可以歸結(jié)為幾點(diǎn),其一,除了偶爾的內(nèi)存申請(qǐng)和銷毀會(huì)導(dǎo)致從進(jìn)程堆中分配和銷毀內(nèi)存塊外,絕大多數(shù)的內(nèi)存申請(qǐng) 和銷毀都由內(nèi)存池在已經(jīng)申請(qǐng)到的內(nèi)存塊中進(jìn)行,而沒有直接與進(jìn)程堆打交道,而直接與進(jìn)程堆打交道是很耗時(shí)的操作;其二,這是單線程環(huán)境的內(nèi)存池,可以看到 內(nèi)存池的Alloc和Free操作中并沒有加線程保護(hù)措施。因此如果類A用到該內(nèi)存池,則所有類A對(duì)象的創(chuàng)建和銷毀都必須發(fā)生在同一個(gè)線程中。但如果類A 用到內(nèi)存池,類B也用到內(nèi)存池,那么類A的使用線程可以不必與類B的使用線程是同一個(gè)線程。

            另外,在第1章中已經(jīng)討論過,因?yàn)閮?nèi)存池技術(shù)使得同類型的對(duì)象分布在相鄰的內(nèi)存區(qū)域,而程序會(huì)經(jīng)常對(duì)同一類型的對(duì)象進(jìn)行遍歷操作。因此在程序運(yùn)行過程中發(fā)生的缺頁應(yīng)該會(huì)相應(yīng)少一些,但這個(gè)一般只能在真實(shí)的復(fù)雜應(yīng)用環(huán)境中進(jìn)行驗(yàn)證。





            回頁首


            6.3 本章小結(jié)

            內(nèi) 存的申請(qǐng)和釋放對(duì)一個(gè)應(yīng)用程序的整體性能影響極大,甚至在很多時(shí)候成為某個(gè)應(yīng)用程序的瓶頸。消除內(nèi)存申請(qǐng)和釋放引起的瓶頸的方法往往是針對(duì)內(nèi)存使用的實(shí)際 情況提供一個(gè)合適的內(nèi)存池。內(nèi)存池之所以能夠提高性能,主要是因?yàn)樗軌蚶脩?yīng)用程序的實(shí)際內(nèi)存使用場(chǎng)景中的某些"特性"。比如某些內(nèi)存申請(qǐng)與釋放肯定發(fā) 生在一個(gè)線程中,某種類型的對(duì)象生成和銷毀與應(yīng)用程序中的其他類型對(duì)象要頻繁得多,等等。針對(duì)這些特性,可以為這些特殊的內(nèi)存使用場(chǎng)景提供量身定做的內(nèi)存 池。這樣能夠消除系統(tǒng)提供的缺省內(nèi)存機(jī)制中,對(duì)于該實(shí)際應(yīng)用場(chǎng)景中的不必要的操作,從而提升應(yīng)用程序的整體性能。



            作者簡(jiǎn)介


            馮 宏華,清華大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士。IBM 中國(guó)開發(fā)中心高級(jí)軟件工程師。 2003 年 12 月加入 IBM 中國(guó)開發(fā)中心,主要從事 IBM 產(chǎn)品的開發(fā)、性能優(yōu)化等工作。興趣包括 C/C++ 應(yīng)用程序性能調(diào)優(yōu),Windows 應(yīng)用程序開發(fā),Web 應(yīng)用程序開發(fā)等。



            徐 瑩,山東大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士。2003 年 4 月加入 IBM 中國(guó)開發(fā)中心,現(xiàn)任 IBM 中國(guó)開發(fā)中心開發(fā)經(jīng)理,一直從事IBM軟件產(chǎn)品在多個(gè)操作系統(tǒng)平臺(tái)上的開發(fā)工作。曾參與 IBM 產(chǎn)品在 Windows 和 Linux 平臺(tái)上的性能優(yōu)化工作,對(duì) C/C++ 編程語言和跨平臺(tái)的大型軟件系統(tǒng)的開發(fā)有較豐富的經(jīng)驗(yàn)。



            程 遠(yuǎn),北京大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士。IBM 中國(guó)開發(fā)中心高級(jí)軟件工程師。2003 年加入 IBM 中國(guó)開發(fā)中心,主要從事IBM Productivity Tools 產(chǎn)品的開發(fā)、性能優(yōu)化等工作。興趣包括 C/C++ 編程語言,軟件性能工程,Windows/Linux 平臺(tái)性能測(cè)試優(yōu)化工具等。



            汪 磊,北京航空航天大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)系碩士,目前是 IBM 中國(guó)軟件開發(fā)中心高級(jí)軟件工程師。從 2002 年 12 月加入 IBM 中國(guó)開發(fā)中心至今一直從事旨在提高企業(yè)生產(chǎn)效率的應(yīng)用軟件開發(fā)。興趣包括 C\C++ 應(yīng)用程序的性能調(diào)優(yōu),Java 應(yīng)用程序的性能調(diào)優(yōu)。



            posted @ 2008-11-17 16:37 micheal's tech 閱讀(416) | 評(píng)論 (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 閱讀(644) | 評(píng)論 (0)編輯 收藏

            0 引言

            0.1 目的

                   本文檔給出設(shè)計(jì)模式之——AbstractFactory模式的簡(jiǎn)化詮釋,并給出其C++實(shí)現(xiàn)。

            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 聯(lián)系作者

            Author

            K_Eckel

            State

            Candidate for Master’s Degree School of

            E_mail

            frwei@whu.edu.cn  

            2 AbstractFactory模式

            2.1 問題

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

                   AbstractFactory模式就是用來解決這類問題的:要?jiǎng)?chuàng)建一組相關(guān)或者相互依賴的對(duì)象。

            2.2 模式選擇

                   AbstractFactory模式典型的結(jié)構(gòu)圖為:


            2-1AbstractFactoryPattern結(jié)構(gòu)圖

                   AbstractFactory模式關(guān)鍵就是將這一組對(duì)象的創(chuàng)建封裝到一個(gè)用于創(chuàng)建對(duì)象的類(ConcreteFactory)中,維護(hù)這樣一個(gè)創(chuàng)建類總比維護(hù)n多相關(guān)對(duì)象的創(chuàng)建過程要簡(jiǎn)單的多。

            2.3 實(shí)現(xiàn)

                   AbstractFactory模式的實(shí)現(xiàn)比較簡(jiǎn)單,這里為了方便初學(xué)者的學(xué)習(xí)和參考,將給出完整的實(shí)現(xiàn)代碼(所有代碼采用C++實(shí)現(xiàn),并在VC 6.0下測(cè)試運(yùn)行)。

            代碼片斷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模式的實(shí)現(xiàn)代碼很簡(jiǎn)單,在測(cè)試程序中可以看到,當(dāng)我們要?jiǎng)?chuàng)建一組對(duì)象(ProductA1,ProductA2)的時(shí)候我們只用維護(hù)一個(gè)創(chuàng)建對(duì)象(ConcreteFactory1),大大簡(jiǎn)化了維護(hù)的成本和工作。

            2.4 討論

                   AbstractFactory模式和Factory模式的區(qū)別是初學(xué)(使用)設(shè)計(jì)模式時(shí)候的一個(gè)容易引起困惑的地方。實(shí)際上,AbstractFactory模式是為創(chuàng)建一組(有多類)相關(guān)或依賴的對(duì)象提供創(chuàng)建接口,而Factory模式正如我在相應(yīng)的文檔中分析的是為一類對(duì)象提供創(chuàng)建接口或延遲對(duì)象的創(chuàng)建到子類中實(shí)現(xiàn)。并且可以看到,AbstractFactory模式通常都是使用Factory模式實(shí)現(xiàn)(ConcreteFactory1)。


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

            0 引言

            0.1 目的

                   本文檔給出設(shè)計(jì)模式之——Factory模式的簡(jiǎn)化詮釋,并給出其C++實(shí)現(xiàn)。

            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       網(wǎng)頁

            0.4 聯(lián)系作者

            Author

            K_Eckel

            State

            Candidate for Master’s Degree School of

            E_mail

            frwei@whu.edu.cn  

             

            2 Factory模式

            2.1 問題

                   在面向?qū)ο笙到y(tǒng)設(shè)計(jì)中經(jīng)常可以遇到以下的兩類問題:

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

                   2)還有一種情況就是在父類中并不知道具體要實(shí)例化哪一個(gè)具體的子類。這里的意思為:假設(shè)我們?cè)陬怉中要使用到類B,B是一個(gè)抽象父類,在A中并不知道具體要實(shí)例化那一個(gè)B的子類,但是在類A的子類D中是可以知道的。在A中我們沒有辦法直接使用類似于new ×××的語句,因?yàn)楦揪筒恢?#215;××是什么。

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

                   1)定義創(chuàng)建對(duì)象的接口,封裝了對(duì)象的創(chuàng)建;

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

            2.2 模式選擇

                   我們通常使用Factory模式來解決上面給出的兩個(gè)問題。在第一個(gè)問題中,我們經(jīng)常就是聲明一個(gè)創(chuàng)建對(duì)象的接口,并封裝了對(duì)象的創(chuàng)建過程。Factory這里類似于一個(gè)真正意義上的工廠(生產(chǎn)對(duì)象)。在第二個(gè)問題中,我們需要提供一個(gè)對(duì)象創(chuàng)建對(duì)象的接口,并在子類中提供其具體實(shí)現(xiàn)(因?yàn)橹挥性谧宇愔锌梢詻Q定到底實(shí)例化哪一個(gè)類)。第一中情況的Factory的結(jié)構(gòu)示意圖為:


            1Factory模式結(jié)構(gòu)示意圖1

                   1所以的Factory模式經(jīng)常在系統(tǒng)開發(fā)中用到,但是這并不是Factory模式的最大威力所在(因?yàn)檫@可以通過其他方式解決這個(gè)問題)。Factory模式不單是提供了創(chuàng)建對(duì)象的接口,其最重要的是延遲了子類的實(shí)例化(第二個(gè)問題),以下是這種情況的一個(gè)Factory的結(jié)構(gòu)示意圖:


            2Factory模式結(jié)構(gòu)示意圖1

                   2中關(guān)鍵中Factory模式的應(yīng)用并不是只是為了封裝對(duì)象的創(chuàng)建,而是要把對(duì)象的創(chuàng)建放到子類中實(shí)現(xiàn):Factory中只是提供了對(duì)象創(chuàng)建的接口,其實(shí)現(xiàn)將放在Factory的子類ConcreteFactory中進(jìn)行。這是圖2和圖1的區(qū)別所在。

            2.3 實(shí)現(xiàn)

             代碼片斷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 模式在實(shí)際開發(fā)中應(yīng)用非常廣泛,面向?qū)ο蟮南到y(tǒng)經(jīng)常面臨著對(duì)象創(chuàng)建問題:要?jiǎng)?chuàng)建的類實(shí)在是太多了。而Factory提供的創(chuàng)建對(duì)象的接口封裝(第一個(gè)功 能),以及其將類的實(shí)例化推遲到子類(第二個(gè)功能)都部分地解決了實(shí)際問題。一個(gè)簡(jiǎn)單的例子就是筆者開開發(fā)Visual CMCS系統(tǒng)的語義分析過程中,由于要為文法中的每個(gè)非終結(jié)符構(gòu)造一個(gè)類處理,因此這個(gè)過程中對(duì)象的創(chuàng)建非常多,采用Factory模式后系統(tǒng)可讀性性和 維護(hù)都變得elegant許多。
                 Factory模式也帶來至少以下兩個(gè)問題:
                 1)如果為每一個(gè)具體的ConcreteProduct類的實(shí)例化提供一個(gè)函數(shù)體,那么我們可能不得不在系統(tǒng)中添加了一個(gè)方法來處理這個(gè)新建的 ConcreteProduct,這樣Factory的接口永遠(yuǎn)就不肯能封閉(Close)。當(dāng)然我們可以通過創(chuàng)建一個(gè)Factory的子類來通過多態(tài)實(shí) 現(xiàn)這一點(diǎn),但是這也是以新建一個(gè)類作為代價(jià)的。
                 2)在實(shí)現(xiàn)中我們可以通過參數(shù)化工廠方法,即給FactoryMethod()傳遞一個(gè)參數(shù)用以決定是創(chuàng)建具體哪一個(gè)具體的Product(實(shí)際上筆者在 Visual CMCS中也正是這樣做的)。當(dāng)然也可以通過模板化避免1)中的子類創(chuàng)建子類,其方法就是將具體Product類作為模板參數(shù),實(shí)現(xiàn)起來也很簡(jiǎn)單。
                 可以看出,F(xiàn)actory模式對(duì)于對(duì)象的創(chuàng)建給予開發(fā)人員提供了很好的實(shí)現(xiàn)策略,但是Factory模式僅僅局限于一類類(就是說Product是一類, 有一個(gè)共同的基類),如果我們要為不同類的類提供一個(gè)對(duì)象創(chuàng)建的接口,那就要用Abstract Factory了。


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

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


            };

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

            如果milkDecorator 設(shè)計(jì)成這樣就不會(huì)違反了封裝的原則。
            基本上只有一個(gè)區(qū)別,就是protect成員能被派生類訪問!而派生類對(duì)private沒有特殊訪問權(quán)!

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

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

            虛線箭頭表示“依賴關(guān)系”,依賴有“使用”的語義,比如患者與醫(yī)生的關(guān)系。
            實(shí)線箭頭表示“帶了導(dǎo)航行的關(guān)聯(lián)關(guān)系”,從一個(gè)類到另一類。
            使用實(shí)線箭頭時(shí)通常會(huì)帶上“多重性”的表達(dá)方式。如:一對(duì)多,一對(duì)一,多對(duì)多等等

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

                  其中,聚合關(guān)系(Aggregation),合成關(guān)系(Composition)屬于關(guān)聯(lián)關(guān)系(Association)。

                  一般關(guān)系表現(xiàn)為繼承或?qū)崿F(xiàn)關(guān)系(is a),關(guān)聯(lián)關(guān)系表現(xiàn)為變量(has a ),依賴關(guān)系表現(xiàn)為函數(shù)中的參數(shù)(use a)。

                  一般化關(guān)系:表示為類與類之間的繼承關(guān)系,接口與接口之間的繼承,類對(duì)接口的實(shí)現(xiàn)關(guān)系。
                  表示方法: 用一個(gè)空心箭頭+實(shí)線,箭頭指向父類。或空心箭頭+虛線,如果父類是接口。

                  關(guān)聯(lián)關(guān)系:類與類之間的聯(lián)接,它使一個(gè)類知道另一個(gè)類的屬性和方法。
                  表示方法:用 實(shí)線+箭頭, 箭頭指向被使用的類。

                  聚合關(guān)系:是關(guān)聯(lián)關(guān)系的一種,是強(qiáng)的關(guān)聯(lián)關(guān)系。聚合關(guān)系是整體和個(gè)體的關(guān)系。關(guān)聯(lián)關(guān)系的兩個(gè)類處于同一層次上,啊聚合關(guān)系兩個(gè)類處于不同的層次,一個(gè)是整體,一個(gè)是部分。
                  表示方法:空心菱形+實(shí)線+箭頭,箭頭指向部分。

                  合成關(guān)系:是關(guān)聯(lián)關(guān)系的一種,是比聚合關(guān)系強(qiáng)的關(guān)系。它要求普通的聚合關(guān)系中代表整體的對(duì)象負(fù)責(zé)代表部分的對(duì)象的生命周期,合成關(guān)系不能共享。
                  表示方法:實(shí)心菱形+實(shí)線+箭頭,

                  依賴關(guān)系:是類與類之間的連接,表示一個(gè)類依賴于另一個(gè)類的定義。例如如果A依賴于B,則B體現(xiàn)為局部變量,方法的參數(shù)、或靜態(tài)方法的調(diào)用。
                  表示方法:虛線+箭頭


            圖一:

            uploads/200706/04_211918_1121523.jpg


            此實(shí)線箭頭表示, 繼承, 從一個(gè)非接口類的繼承.

            圖二:
            uploads/200706/04_212112_1121525gl.jpg


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

            圖三:
            uploads/200706/04_213002_1121526dxgl.jpg


            那條連線表示單向關(guān)聯(lián):
            基本的意義跟上面的是一樣的, 唯一不同的是, 右邊的類對(duì)左邊的類是一無所知的.

            圖四:
            uploads/200706/04_213232_1121527rjb.jpg


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

            圖五:
            uploads/200706/04_213441_1121529xjc.gif


            如此虛線的箭頭表示實(shí)現(xiàn)一個(gè)接口.

            圖六:
            uploads/200706/04_213626_11215210gll.jpg


            水平的連線還是表示上面所說的關(guān)聯(lián), 但從關(guān)聯(lián)連線中引伸出來的虛線, 這意味當(dāng)Flight類的一個(gè)實(shí)例關(guān)聯(lián)到 FrequentFlyer 類的一個(gè)實(shí)例時(shí),將會(huì)產(chǎn)生 MileageCredit 類的一個(gè)實(shí)例.

            圖七:
            uploads/200706/04_213911_11215211jbjh.jpg


            帶菱形的箭頭表示基本聚合, 由上圖知道, Wheel類扮演wheels角色, 聚合4個(gè)到Car對(duì)象里面去,
            空心的菱形表示W(wǎng)heel對(duì)象并不隨Car的創(chuàng)建而創(chuàng)建,銷毀而銷毀.

            圖八:
            uploads/200706/04_214248_11215212zhjh.jpg


            意義和上面類似, 唯一不同的是, 實(shí)心菱形表示Department對(duì)象隨Company對(duì)象的創(chuàng)建而創(chuàng)建,銷毀而銷毀.

            圖九:
            uploads/200706/04_214435_11215213fs.gif


            表示反射關(guān)聯(lián), 顯示一個(gè)Employee類如何通過manager / manages角色與它本身相關(guān)。當(dāng)一個(gè)類關(guān)聯(lián)到它本身時(shí),這并不意味著類的實(shí)例與它本身相關(guān),而是類的一個(gè)實(shí)例與類的另一個(gè)實(shí)例相關(guān)。

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

            0 引言

            0.1 目的

                   本文檔給出設(shè)計(jì)模式之——Observer模式的簡(jiǎn)化詮釋,并給出其C++實(shí)現(xiàn)。

            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 聯(lián)系作者

            Author

            K_Eckel

            State

            Candidate for Master’s Degree School of

            E_mail

            frwei@whu.edu.cn  

            2 Observer模式

            2.1 問題

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

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

            2.2 模式選擇

                   Observer模式典型的結(jié)構(gòu)圖為:


            2-1Observer Pattern結(jié)構(gòu)圖

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

            2.3 實(shí)現(xiàn)

                   Observer模式的實(shí)現(xiàn)有些特點(diǎn),這里為了方便初學(xué)者的學(xué)習(xí)和參考,將給出完整的實(shí)現(xiàn)代碼(所有代碼采用C++實(shí)現(xiàn),并在VC 6.0下測(cè)試運(yùn)行)。

            代碼片斷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,創(chuàng)建
             _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++)
             {
              //關(guān)于模板和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作為參數(shù),這樣可以讓一個(gè)View屬于多個(gè)的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作為參數(shù),這樣可以讓一個(gè)View屬于多個(gè)的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調(diào)用

             sub->Notify();

             return 0;
            }

            在Observer模式的實(shí)現(xiàn)中,Subject維護(hù)一個(gè)list作為存儲(chǔ)其所有觀察者的容器。每當(dāng)調(diào)用Notify操作就遍歷list中的Observer對(duì)象,并廣播通知改變狀態(tài)(調(diào)用Observer的Update操作)。目標(biāo)的狀態(tài)state可以由Subject自己改變(示例),也可以由Observer的某個(gè)操作引起state的改變(可調(diào)用Subject的SetState操作)。Notify操作可以由Subject目標(biāo)主動(dòng)廣播(示例),也可以由Observer觀察者來調(diào)用(因?yàn)镺bserver維護(hù)一個(gè)指向Subject的指針)。

                   運(yùn)行示例程序,可以看到當(dāng)Subject處于狀態(tài)“old”時(shí)候,依賴于它的兩個(gè)觀察者都顯示“old”,當(dāng)目標(biāo)狀態(tài)改變?yōu)?#8220;new”的時(shí)候,依賴于它的兩個(gè)觀察者也都改變?yōu)?#8220;new”。

            2.4 討論

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

             


            Java中實(shí)現(xiàn)MVC的UML圖。

            這里的Observer就是觀察者,Observable則充當(dāng)目標(biāo)Subject的角色。

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


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

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

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

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

            關(guān)鍵是:在分離式編譯的環(huán)境下,編譯器編譯某一個(gè).cpp文件時(shí)并不知道另一個(gè).cpp文件的存在,也不會(huì)去查找[當(dāng)遇到未決符號(hào)時(shí)它會(huì)寄希望于連 接器]。這種模式在沒有模板的情況下運(yùn)行良好,但遇到模板時(shí)就傻眼了,因?yàn)槟0鍍H在需要的時(shí)候才會(huì)具現(xiàn)化出來,所以,當(dāng)編譯器只看到模板的聲明時(shí),它不能 具現(xiàn)化該模板,只能創(chuàng)建一個(gè)具有外部連接的符號(hào)并期待連接器能夠?qū)⒎?hào)的地址決議出來。然而當(dāng)實(shí)現(xiàn)該模板的.cpp文件中沒有用到模板的具現(xiàn)體時(shí),編譯器 懶得去具現(xiàn),所以,整個(gè)工程的.obj中就找不到一行模板具現(xiàn)體的二進(jìn)制代碼,于是連接器也黔

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


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

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

            鏈接錯(cuò)誤

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

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

                     ·對(duì)于全局變量和(非內(nèi)聯(lián))函數(shù),只有聲明放在頭文件中,而定義放在點(diǎn)C文件中,這些文件具有.cpp(或者.C, .c, .cc, .cxx)擴(kuò)展名。
             

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

            由于以上組織方式約定的影響,模板編程新手往往會(huì)犯一個(gè)同樣的錯(cuò)誤。下面這一小段程序反映了這種錯(cuò)誤。就像對(duì)待“普通代碼”那樣,我們?cè)陬^文件中定義模板:
             

            // 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()聲明了一個(gè)簡(jiǎn)單的輔助函數(shù)用來打印一些類型信息。函數(shù)的定義放在點(diǎn)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;

            }

             

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

            最后,我們?cè)诹硗庖粋€(gè)點(diǎn)C文件中使用我們的模板,在這個(gè)文件中模板聲明被#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)很可能會(huì)接受這個(gè)程序,沒有任何問題,但是鏈接器(Linker)大概會(huì)報(bào)告一個(gè)錯(cuò)誤,指出缺少函數(shù)print_typeof()的定義。

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

            頭文件中的模板

            解決上面這個(gè)問題的通用解法是,采用與我們使用宏或者內(nèi)聯(lián)函數(shù)相同的方法:我們將模板的定義包含進(jìn)聲明模板的頭文件中。對(duì)于我們的例子,我們可以通 過將#i nclude "myfirst.cpp"添加到myfirst.hpp文件尾部,或者在每一個(gè)使用我們的模板的點(diǎn)C文件中包含myfirst.cpp文件,來達(dá)到目 的。當(dāng)然,還有第三種方法,就是刪掉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

             

            這種組織模板代碼的方式就稱作包含模式。經(jīng)過這樣的調(diào)整,你會(huì)發(fā)現(xiàn)我們的程序已經(jīng)能夠正確編譯、鏈接、執(zhí)行了。

            從這個(gè)方法中我們可以得到一些觀察結(jié)果。最值得注意的一點(diǎn)是,這個(gè)方法在相當(dāng)程度上增加了包含myfirst.hpp的開銷。在這個(gè)例子中,這種開 銷并不是由模板定義自身的尺寸引起的,而是由這樣一個(gè)事實(shí)引起的,即我們必須包含我們的模板用到的頭文件,在這個(gè)例子中 是<iostream>和<typeinfo>。你會(huì)發(fā)現(xiàn)這最終導(dǎo)致了成千上萬行的代碼,因?yàn)橹T 如<iostream>這樣的頭文件也包含了和我們類似的模板定義。

            這在實(shí)踐中確實(shí)是一個(gè)問題,因?yàn)樗黾恿司幾g器在編譯一個(gè)實(shí)際程序時(shí)所需的時(shí)間。我們因此會(huì)在以后的章節(jié)中驗(yàn)證其他一些可能的方法來解決這個(gè)問題。但無論如何,現(xiàn)實(shí)世界中的程序花一小時(shí)來編譯鏈接已經(jīng)是快的了(我們?cè)?jīng)遇到過花費(fèi)數(shù)天時(shí)間來從源碼編譯的程序)。

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

            另一個(gè)觀察結(jié)果是,非內(nèi)聯(lián)模板函數(shù)與內(nèi)聯(lián)函數(shù)和宏的最重要的不同在于:它并不會(huì)在調(diào)用端展開。相反,當(dāng)模板函數(shù)被具現(xiàn)化時(shí),會(huì)產(chǎn)生此函數(shù)的一個(gè)新的 拷貝。由于這是一個(gè)自動(dòng)的過程,編譯器也許會(huì)在不同的文件中產(chǎn)生兩個(gè)相同的拷貝,從而引起鏈接器報(bào)告一個(gè)錯(cuò)誤。理論上,我們并不關(guān)心這一點(diǎn):這是編譯器設(shè) 計(jì)者應(yīng)當(dāng)關(guān)心的事情。實(shí)際上,大多數(shù)時(shí)候一切都運(yùn)轉(zhuǎn)正常,我們根本就不用處理這種狀況。然而,對(duì)于那些需要?jiǎng)?chuàng)建自己的庫的大型項(xiàng)目,這個(gè)問題偶爾會(huì)顯現(xiàn)出 來。
             

            最后,需要指出的是,在我們的例子中,應(yīng)用于普通模板函數(shù)的方法同樣適用于模板類的成員函數(shù)和靜態(tài)數(shù)據(jù)成員,以及模板成員函數(shù)。



            posted @ 2008-10-09 17:23 micheal's tech 閱讀(1896) | 評(píng)論 (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 閱讀(861) | 評(píng)論 (0)編輯 收藏

            句柄類是存儲(chǔ)和管理基類指針的一個(gè)類
            需要句柄類的背景:
            1)在對(duì)安全要求很高的領(lǐng)域,即使核心實(shí)現(xiàn)已經(jīng)封閉在庫中不可見,但頭文件中變量定義仍可能曝露一些內(nèi)部信息
            2)在設(shè)計(jì)初期、實(shí)現(xiàn)部分會(huì)經(jīng)常變動(dòng),甚至頭文件中變量定義也需要經(jīng)常變動(dòng),因此在重編譯的時(shí)候頭文件也需要編譯,
            有時(shí)候?qū)е戮幾g時(shí)間過長(zhǎng)。
            句柄類就是為了解決這類問題
            // Handle.h
            class Implement; //這里是對(duì)真正實(shí)現(xiàn)功能的類的聲明

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




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

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

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

            這其實(shí)就是接口與實(shí)現(xiàn)的分離,


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

            僅列出標(biāo)題
            共8頁: 1 2 3 4 5 6 7 8 
            午夜精品久久久久久影视777| 伊人久久大香线蕉AV一区二区| 亚洲精品美女久久777777| 亚洲午夜久久久久久久久电影网| 囯产精品久久久久久久久蜜桃 | 久久夜色精品国产噜噜噜亚洲AV| 99国产欧美精品久久久蜜芽| 国产精品久久久久久久久鸭 | 久久综合九色欧美综合狠狠| 波多野结衣久久一区二区 | 99久久国语露脸精品国产| 国产精品永久久久久久久久久| 一本大道久久东京热无码AV| 久久久久久久人妻无码中文字幕爆 | 国产69精品久久久久久人妻精品| 国产成人久久精品一区二区三区 | 久久精品国产亚洲Aⅴ香蕉| 久久伊人精品一区二区三区| 岛国搬运www久久| 欧美喷潮久久久XXXXx| 久久综合九色综合欧美就去吻| 2021少妇久久久久久久久久| 日韩欧美亚洲综合久久| 国产亚洲精午夜久久久久久| 久久国产高潮流白浆免费观看| 伊人久久大香线蕉精品不卡 | 久久精品草草草| 无码精品久久久天天影视| 性做久久久久久久久久久| 99久久精品国产一区二区| 国产高潮国产高潮久久久| 777午夜精品久久av蜜臀| 人妻无码久久精品| 狠狠综合久久综合中文88| 狠狠干狠狠久久| 国产精品天天影视久久综合网| 久久精品久久久久观看99水蜜桃| 久久久久亚洲爆乳少妇无| 香蕉aa三级久久毛片| 久久亚洲精品国产亚洲老地址| 精品久久久久久无码人妻蜜桃|