• <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>

            Note of Justin

            關于工作和讀書的筆記

              C++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              47 Posts :: 0 Stories :: 45 Comments :: 0 Trackbacks

            留言簿(14)

            搜索

            •  

            積分與排名

            • 積分 - 52490
            • 排名 - 433

            最新評論

            閱讀排行榜

            評論排行榜

            [原創文章歡迎轉載,但請保留作者信息]
            Justin 于 2009-12-20

            大師說了,C++的設計還是有缺陷的:它無法把接口(interface)的設計和實現(implementation)的設計完全劃分開來。比如說在一個類的(接口)聲明當中,總是或多或少的會泄漏一些實現上的細節,雖然這樣做與接口的設計并沒有太多聯系。
            有同學說應該多放些代碼一起炒冷飯,是個好主意,下面是書中的修改版本,大致是一樣的。

            class ?AClass {
            public :
            ???
            void ?interface_1();
            ???std::
            string ?interface_2();
            ???
            // ..
            private :
            ???
            // ?implementation?details?are?leaking?as?below..
            ???std:: string ?internalData_1;
            ???BClass?internalData_2;
            ???
            // ..
            }

            這些實現上的細節往往需要引用其他頭文件中相關對象的定義(比如說下面的代碼),從而產生了對這些頭文件的(在編譯時的)依賴。因此每次這些文件中的某個有變化時,依賴它的所有文件都需要重新編譯。

            #include? < string >
            #include?
            " BClass.h "
            // ..

            【注意】這里貌似邏輯不是很順:就算沒有那些私有成員的聲明,接口函數的返回值如果是string或是BClass等類型,不還是一樣需要依賴引用其他頭文件嗎?其實這是兩種不一樣的情況,實現和接口。前面說的實現細節的泄漏是會導致編譯依賴的,因為編譯器需要了解這些類型對象的大小進而為其分配內存空間;但是接口,比如說函數的返回值或是參數表中的參數,就不需要編譯器去考慮分配內存的問題,因此也就沒有所謂的編譯依賴了。
            問題知道了,那么解決辦法呢,大師提出“骨肉分離法”(嗯……其實是我的杜撰@#¥%):將聲明(declaration)和定義(definition)分開。

            呃……下面的比喻,最好吃完飯再繼續。
            如果說接口是一個類的骨架,那么實現就是他的血肉;如果說聲明讓你摸到了骨頭,那么定義應該就是血和肉生長的地方。
            根據骨肉分離法,對于一個AClass類,第一步先把血肉(定義/實現)剝離開,只留下骨架。然后找個盒子(新建一個類,比如說AClassImpl),把血肉放進去。
            接下來還有一步,在骨頭盒子里(原AClass類)加一條繩子連著血肉盒子(一個指向AClassImpl的指針),這樣才不至于讓骨肉真正的分離,只要找到了骨頭盒子,就一定能找到血肉盒子,然后對于這個“可憐”的AClass來說,它的全部“零件”都是完整的,啥也沒丟,但是做到了骨肉分離。

            也做到了沒有編譯依賴。
            因為對于AClass的用戶來說,他們面對的將是一個沒有定義的類,這個類的后繼改動,只要不涉及接口的改動,都不會導致用戶程序的重新編譯。
            看到這里想想工作時看到的代碼,原來前輩也有看過啊……
            對比前面的例程,給一個“骨肉分離”了的版本吧:

            class ?AClassImpl {
            // ..
            private :
            ???
            // ?implementation?details?are?moved?here..
            ???std:: string ?internalData_1;
            ???BClass?internalData_2;
            // ..
            }


            class ?AClass {
            public :
            ???
            void ?interface_1();
            ???std::
            string ?interface_2();
            ???
            // ..
            private :
            ???
            // ?there?is?only?a?pointer?to?implementation
            ???std::tr1::shared_ptr < AClassImpl > ?pImpl;
            }


            // a?constructor:?instantiations?of?AClass?and?AClassImpl?should?always?be?bound?together.
            AClass::AClass( // ..)?:?pImpl(new?AClassImpl( // ..))
            {
            ???
            // ..
            }

            前面的文字是自己的理解,而大師的真言是這樣的:

            • 如果可以用指針/引用的話,就不用對象。
            • 如果可以做到僅依賴聲明,就不要依賴定義。
            • 為定義和聲明分別準備兩個頭文件。這樣一來,用戶就可以很簡單做到上面兩點。

            如果覺得骨肉分離太殘忍,大師還有另外一個工具:工廠(factory)。
            第二種方法中,抽象類/接口類提供了所有接口的純虛函數形式:會有該類的子類去實現這些接口。與此同時,在抽象類/接口類中還會有一個靜態(static)的工廠函數(比如create()/produce()/factory()……),這個函數實際上起到了構造函數的作用,它“制造”出子類對象來完成真正的任務,同時返回這個對象的指針(通常是智能指針如shared_ptr)。憑借這個返回的指針就可以進行正常的操作,同時不會有編譯依賴的擔心。一個簡陋的代碼見下:

            class ?AClass: public ?AClassFactory {
            public :
            ???AClass()?
            {}
            ???
            void ?interface_1();
            ???std::
            string ?interface_2();
            ???
            virtual ? ~ AClass();
            // ..
            }


            class ?AClassFactory {
            public :
            ???
            virtual ? void ?interface_1()? = ? 0 ;
            ???
            virtual ?std:: string ?interface_2()? = ? 0 ;
            ???
            // ..
            ??? virtual ? ~ AClassFactory() { /* .. */ }
            ???
            static ?std::tr1::shared_ptr < AClassFactory > ?Produce( /* .. */ )
            ???
            {
            ??????
            // this?factory?function?could?be?more?complicated?in?practice..
            ?????? return ?std::tr1::shared_ptr < AClassFactory > ( new ?AClass);
            ???}

            // ..
            }



            // AClassFactory?could?be?used?in?this?way..
            std::tr1::shared_ptr < AClassFactory > ?pAClassObject;
            pAClassObject?
            = ?AClassFactory::Produce( /* .. */ );
            // pAClassObject->..

            無論是骨肉分離法還是工廠模式,都可以去除編譯依賴。代價是有的,要為之付出一點點額外代碼執行的時間和空間。這個代價又可以通過內聯函數(inline function)來減小一些。(不過有聽過這種說法:大部分的編譯器都會將短小的函數自動轉成內聯函數的)
            盡管如此,只有在以上做法很明顯地降低了系統的性能的情況下,才可以放棄分離實現和接口的努力。
            這是大師的忠告。
            posted on 2010-02-01 09:06 Justin.H 閱讀(2064) 評論(2)  編輯 收藏 引用 所屬分類: Effective C++ 炒冷飯

            Feedback

            # re: 讀書筆記:Effective C++ 炒冷飯 - Item 31 減少文件間的編譯依賴 2012-10-02 20:34 xiaolong
            只能說降低編譯依賴,但是實際工作中編譯依賴簡直太大了,而且很多情況下無法避免的吧!  回復  更多評論
              

            # re: 讀書筆記:Effective C++ 炒冷飯 - Item 31 減少文件間的編譯依賴[未登錄] 2014-11-18 17:00 liu
            寫的不錯,血肉的說法太瘆人了,改成肉吧  回復  更多評論
              

            97久久婷婷五月综合色d啪蜜芽| 国产精品免费久久| 久久66热人妻偷产精品9| 精品999久久久久久中文字幕| 久久91亚洲人成电影网站| 精品久久人人爽天天玩人人妻| 思思久久好好热精品国产| 久久AV高清无码| 热RE99久久精品国产66热| 国产精品欧美久久久天天影视 | 久久综合久久美利坚合众国| 天天躁日日躁狠狠久久| 久久精品国产99国产精品| 一本色道久久88精品综合| 久久久久久久亚洲精品| 久久99精品久久久久久久久久| 亚洲国产精品成人AV无码久久综合影院| 亚洲午夜久久久影院伊人| 欧美色综合久久久久久| 久久久久国产精品| 99久久免费国产特黄| 欧美午夜精品久久久久免费视| 久久综合九色综合久99| 热久久国产精品| 久久最新精品国产| www性久久久com| 久久久精品人妻一区二区三区蜜桃| 亚洲精品乱码久久久久久不卡| 国产精品18久久久久久vr| 久久久久99精品成人片欧美| 无码人妻久久一区二区三区蜜桃| 久久99精品久久久久久9蜜桃| 久久精品国产91久久综合麻豆自制| 久久亚洲中文字幕精品有坂深雪| 久久精品国产久精国产果冻传媒| 模特私拍国产精品久久| 狠狠综合久久AV一区二区三区| 久久综合九色综合网站| 午夜精品久久久久久久| 久久精品欧美日韩精品| 狠狠色丁香婷婷综合久久来|