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

            woaidongmao

            文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
            隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
            數據加載中……

            Generic:Traits on Steroids

                  以前我們介紹了traits的一些基本用法,大家感覺如何?這次我們接著介紹traits的一些更為有趣的用途,讓大家興奮一下(Steroid是類固醇,常見的興奮劑)。
               
            在上期的Generic<Programming>[1]中,我們討論了traits模板和traits類。這篇文章進一步討論traits對象和全層次(hierarchy-widetraits
                Traits
            技術很有用,但是什么時候你需要這種非凡的靈活性呢?如果你用了traits,你怎么才能避免手工向現有類層次中的大量的類添加traits 的苦差事呢?這篇文章以上一次的SmartPtr為例,解答這些問題。特別是介紹了全層次(hierarchy-widetraits,一項非常 coolC++新技術,可以讓你一下子為整個類層次而不僅是單個類定義traits
               
            回到SMARTPTR
               
            上一次的專欄里介紹了一個smart pointer,它可以根據客戶對模板實例化的方式不同而用于單線程或多線程的代碼中。再來看一下SmartPtr的定義:

            template <class T, class RCTraits = RefCountingTraits<T> >
            class SmartPtr
            {
            ...
            };

            RefCountingTraits可以對SmartPtr進行定制,以適應不同類型T所使用的引用計數的語法和語義。如果你要在單線程代碼中用 SmartPtrRefCountingTraits就可以了。否則,你必須另外提供一個traits類(MtRefCountingTraits)作 為第二個模板參數。MtRefCountingTraits保證在多線程情況下,引用計數是安全的。
            class MtRefCountingTraits
            {
            static void Refer(Widget* p)
            {
            // serialize access
            Sentry s(lock_);
            p->AddReference();
            }
            static void Unrefer(Widget* p)
            {
            // serialize access
            Sentry s(lock_);
            if (p->RemoveReference() == 0)
            delete p;
            }
            private:
            static Lock lock_;
            };

            對于單線程的widget,客戶代碼可以用SmartPtr<Widget>,對于多線程的widget,可以用SmartPtr< Widget, MtRefCountingTraits>。如果沒有上一篇文章最后留下的那個問題,事情就這樣簡單。那個問題是:在多線程版的SmartPtr 里,哪一部分還是低效的?
            正如很多讀者指出的那樣,問題在于MtRefCountingTraits用了類級別的加鎖操作。Herb Sutter形象地說:Static lock, bad juju!juju,符咒】當你進行串行化的操作時,比如MtRefCountingTraits::Refer(),類級別的鎖會把 MtRefCountingTraits類的所有對象都鎖住,因為lock_是所有MtRefCountingTraits實例共享的靜態變量。

            如果你有很多線程頻繁的操作widgetsmart pointer,這可能會成為程序低效的一個根源。原本可能完全無關的線程在復制SmartPtr<Widget, MtRefCountingTraits>對象時,也必須排隊等待。

            在對象級別上加鎖是解決這個問題的一個方法。要使用對象級別上的鎖,只需把MtRefCountingTraits的成員lock_改成非靜態的普通成員 變量,就可以對每一個對象單獨加鎖。但是這個方法的缺點是每個對象都因為增加了一個lock_變量而變大了。讓我們來實現對象級別加鎖的smart pointer

            TRAITS
            對象
            當我們把對象級別加鎖的方法運用到SmartPtr上時,我們遇到了一個問題。讓我們再來看一下SmartPtr的析構函數的定義:

            template <class T, class RCTraits = RefCountingTraits<T> >
            class SmartPtr
            {
            private:
            T* pointee_;
            public:
            ...
            ~SmartPtr()
            {
            RCTraits::Unrefer(pointee_);
            }
            };

            正如你所看到的那樣,根本沒有RCTraits的對象。SmartPtr的析構函數用靜態函數的語法調用RCTraits::Unrefer() SmartPtrRCTraits作為只有兩個靜態函數的包裝來使用。現在traits類需要保存一些狀態,所以我們開始討論如何保存traits對 象。顯然,保存traits對象的地方就在SmartPtr對象里,因此我們可以這樣修改代碼:
            template <class T, class RCTraits = RefCountingTraits<T> >
            class SmartPtr
            {
            private:
            T* pointee_;
            RCTraits rcTraits_;
            public:
            ...
            ~SmartPtr()
            {
            rcTraits_.Unrefer(pointee_);
            }
            };

            現在,SmartPtr擁有了一個Lock對象,并使用這個對象完成對象級別的加鎖操作,這正是我們想要的。屬于不同線程的SmartPtr對象不再共享任何數據,因此不會有任何同步的問題。問題解決了。
            然而,SmartPtr變得更大了。這是顯然的,我聽到你說,我們首先要保證的就是多線程的SmartPtr擁有一個Lock對象。但是,不僅僅是多線程SmartPtr變大了,單線程的SmartPtr也變大了,盡管沒有任何附加的數據(回憶一下,RefCountingTraits沒有任何數 據成員)。這是為什么呢?因為C++中空對象的大小也不是0。這條規則在C++語言的很多地方都是合理的。(比如,如果有大小為0的對象的話,你怎樣才能 建立這樣的對象的數組?)

            不管這條規則是否明智,至少在現在這種情況下,它對我們是不利的。SmartPtr<Something, RefCountingTraits<Something> >要比一個單純的指向T的指針大,這是不應該的。現在單線程的SmartPtr的大小至少是sizeof(T*)+1,但通常由于字對齊和字節填充的原因,最終SmartPtr對象大小可能會在2*sizeof(T*)左右。如果你有很多單線程的SmartPtr,尺寸增加的代價會變得很顯著,更不 用說通過傳值方式傳遞SmartPtr的附加消耗了。

            幸運的是,C++標準中有另一條關于對象大小的規則,可以幫助我們解決這個問題。這就是空基類優化(empty base optimization)。如果類D的基類B是空的(沒有非靜態數據成員),那么D對象中的B子對象的有效大小可以是0。這并沒有違反前面那條規則,因 為B子對象被包含于D對象中;當然,如果你抽出一個單獨的B對象時,它還是有非0的大小。你是否可以使用空基類優化取決于你的編譯器,因為這條規則的實現是可選,而不是必需的。MetrowerksCode Warrior 5.xMicrosoft Visual C++ 6.0都實現了空基類優化。還有其他地方也需要使用這個優化。(注:這幾個編譯器里所包含的Standard C++ Library中,在containers的實現時利用了空基類優化。每一個標準的container都聚合了一個allocator對象,缺省的 allocator通常是一個空類【編者:請參考《C++空成員優化》一文】。)

            把空基類優化應用到前面的SmartPtr代碼中,我們可以讓SmartPtr繼承RCTraits,而不是用聚合。通過這種方式,如果RCTraits是空的,編譯器會通過優化去掉多余的空間;如果RCTraits不是空的,那么結果和聚合的情況一樣。

            我們應該用那種繼承呢?privateprotected,還是public?不要忘了這只是一種實現上的優化,而不是概念的變化。不管怎么說,SmartPtr不是一個RCTraits。因此,最好的選擇是私有繼承。

            template <class T, class RCTraits = RefCountingTraits<T> >
            class SmartPtr : private RCTraits
            {
            private:
            T* pointee_;
            public:
            ...
            ~SmartPtr()
            {
            RCTraits::Unrefer(pointee_);
            }
            };

            這只是利用繼承來優化對象大小的一個技巧。有趣的是,我們又回到了用兩個冒號的寫法,因為現在RCTraitsSmartPtr的基類。
            traits需要保持狀態時,就需要用traits對象了。Traits對象可以是其他對象的一部分,也可以作為參數傳遞。當traits對象可能為空時,也許以可以考慮用繼承的技巧來優化對象的內存布局,當然你的編譯器要支持空基類優化。

            定義:traits對象是traits類的一個實例。
            Definition: A traits object is an instance of a traits class.

            插曲
            Traits
            模板, traits, traits對象……當我們的討論從純粹的靜態代碼生成方式轉變到具有狀態的實體時,我們的表達方式也從最靜態的方式(模板)發展到具有更多動態特性的方式(完整的traits對象)。Traits模板完全是一種編譯時的機制;它們在編譯結束前就已經消失了。在另一個極端,traits對象是具有狀態和行 為的動態實體。

            更進一步的動態化是使用多態traitstraits對象的指針或者引用。但那已經超出traits的范疇了。確切地說,多態traits是一個策略(Strategy)設計模式。[2]

            使用能滿足要求的任何一種traits機制,并且盡可能選擇靜態的方案。相對于運行時的解決方案,我們一般更傾向于選擇編譯時的解決方案。編譯時的解決方案意味著:編譯器會對代碼進行更好的檢查,并且往往生成的代碼有更好的效率。當然,另一方面,動態【注:dynamism,這里一語雙關,也可解釋為活力,有生氣】為生活帶來情趣。

            全層次TRAITS
            Traits
            往往不是只用于單個類型,而是用于整個類層次。例如,引用計數的方法通常對于整個類層次都是一樣的。如果不用對于每個類都手工添加 traits,而能夠定義一個traits可以用于整個類層次,那就好了。但是,traits技術的基礎模板對于繼承是一無所知的。這怎么辦呢?

            也許一個好設計的首要準則是要有靈活性,不要局限于一種策略。解決設計問題就像攻打堅固的城堡:如果一個策略不行,最好就換另外一個。一個壞的策略可能也能解決問題,但是比其他方法代價更高。

            根據這個想法,我們重新理一下思路。我們需要找一種在類型層次中保持不變的東西,用它來建立一個類模板。你猜那是什么?嵌套類(在類中定義的類)!除非你重新定義,嵌套類在繼承過程中是不變的。嵌套類可以像其他符號一樣被繼承。這看上去是個值得一試的方法。為了能自動加上嵌入的類型定義,我們先做一個簡單 的模板:

            template <class T>
            struct HierarchyRoot
            {
            // HierarchyId is a nested class
            struct HierarchyId {};
            };

            比如說我們有一個以Shape為根的類層次(圖1)。為了表示Shape是根,你可以讓它繼承Hierarchy<Shape>,如下所示。其他類不變。



            1 Shape層次

            class Shape : public HierarchyRoot<Shape>
            {
            ...
            };
            class Rectangle : public Shape
            {
            ...
            };

            如果你想防止從ShapeHierarchyRoot<Shape>的隱式類型轉換(通常也是不希望的),你可以這樣定義Shape
            class Shape : private HierarchyRoot<Shape>
            {
            ...
            public:
            using HierarchyRoot<Shape>::HierarchyId;
            };

            我們得到一個關鍵的結果:Rectangle::HierarchyIdShape::HierarchyId是同樣的類型。不論你直接或者間接地從Shape派生新類,只要你不重新定義符號HierarchyId,這個符號代表的類型就在整個繼承體系中保持不變。
            要設計一個使用全層次traitsSmartPtr和設計使用普通traitsSmartPtr一樣簡單。你只要用T::HierarchyId代替T就行了,象這樣:

            template <class T, class RCTraits = RefCountingTraits<typename T::HierarchyId> >
            class SmartPtr
            {
            ...
            };

            現在,假設在你的應用程序中有兩個類層次關系:一個以Shape為根,另一個以Widget為根。象Shape一樣,WidgetHierarchyRoot<Widget>繼承。現在你可以這樣為兩個類層次特化RefCountingTraits
            template <>
            class RefCountingTraits<Shape::HierarchyId>
            {
            ...
            };
            template <>
            class RefCountingTraits<Widget::HierarchyId>
            {
            ...
            };

            就是這樣,上面的traits可以正確的應用于在兩個類繼承體系中的類,甚至對還沒有定義的類也沒有問題。下面兩節中將指出,全層次traits是相當靈活的。
            定制全層次TRAITS
            簡單的traits可以為每個類型提供特化;全層次traits為每個類層次提供特化。有時候你可能遇到介于兩者之間的情況:你為整個繼承體系提供了traits模板,同時也要對體系中單獨的一個或兩個類型進行特化。

            你可以這樣定義traits模板來達到目的:

            template <class HierarchyId>
            class HierarchyTraits
            {
            ... most general traits here ...
            };
            template <class T>
            class Traits
            : public HierarchyTraits<T::HierarchyId>
            {
            // empty body - inherits all symbols from base class
            };

            這個traits模板怎樣工作呢?客戶代碼可以這樣使用:Traits<Shape>Traits<Circle>等。若想特 化整個Shape層次的traits,我們可特化HierarchyTraits<Shape::HierarchyId>。缺省情況下,因為Traits<T>繼承HierarchyTraits<T::HierarchyId>,所有Shape的派生類會使用 HierarchyTraits<T::HierarchyId>里定義的traits。(我敢打賭,如果你跟蹤所有這些符號的來源,你會得到很多樂趣。其實這很簡單,HierarchyTraits對應于整個層次,Traits對應于每個類型。)
            如果你想特化一個特定的類的traits,比如說Ellipse,你可以直接特化Traits模版:

            template <>
            class Traits<Ellipse>
            {
            ... specialized stuff for Ellipse ...
            };

            你可以選擇是否繼承HierarchyTraits<Ellipse>。如果你只想重寫一兩個符號,你可以選擇繼承;如果你想完全重寫Ellipsetraits,你可以選擇不繼承。這完全取決于你。
            對于剛才提到的繼承的使用還有一點說明:從動態多形的觀點來看,事實上Traits<T>繼承HierarchyTraits<T:: HierarchyId>是不恰當的,因為HierarchyTraits不是一個具有多態性的基類。我們在這里用繼承是因為另一個理由:把繼承作 為符號傳遞的工具,目的是讓Traits<T>具有HierarchyTraits<T::HierarchyId>里定義的所有符號。繼承不僅可以用來實現動態的多態性,也可以用來在編譯時操作類型。

            用這一節里介紹的Traits-HierarchyTraits方法,可以對類層次traits根據每個類型進行特化。在上面討論的例子中, Traits<Rectangle>Traits<Circle>用的是共同的HierarchyTraits< Shape::HierarchyId>,而Traits<Ellipse>用的是特化了的Traits< Ellipse>。實現這一切不需要做太多的手腳。

            子層次的TRAITS
            假設你要對于Shape的類層次的一個子層次重新定義traits,比如圖中以Bitmap為根的子樹(圖2)。因此你需要特化Bitmap和它的直接或間接子類的traits

            為了達到這個目的,你可以讓Bitmap同時繼承ShapeHierarchyRoot<Bitmap>。然后你必須通過using語句來消除符號HierarchyId的二義性,像這樣:



            2 Shape層次的Bitmap子層次

            class Bitmap : public Shape,
            public HierarchyRoot<Bitmap>
            {
            ...
            public:
            using HierarchyRoot<Bitmap>::HierarchyId;
            };

            通過using語句,Bitmap類會優先使用HierarchyRoot<Bitmap>::HierarchyId,而不是 Shape::HierarchyId。這樣你可以用Bitmap::HierarchyId來特化traits,并且Bitmap的子類也將用這個特 化,除非你為某個子層次又定義了不同的traits
            注意事項
            全層次traits的最大的缺點是需要修改類層次中的基類,而你有時候不能做到這點。還好,你會得到一個編譯錯誤("Class Widget does not define a type HierarchyId"),而不是運行時的錯誤。

            你可以對于無意義類型(如void)特化全層次traits模板,這樣可以在一定程度上解決這個問題。對于你不能修改的類層次,你可以用HierarchyTraits<void>。雖然不是很靈活,但在開發受阻時,也不失為一個可行的方法。

            還有其它不用介入類層次來實現全層次traits的方法,但那些方法往往更脆弱,并且暴露出各種錯誤。我始終歡迎讀者提出各種建議。

            結論
            trait必須保持某些狀態的時候,就需要用到traits對象。如果trait類的狀態是可選的(有些traits有狀態,有些沒有),那么最好是通過繼承的技巧,利用空基類優化(如果可能的話)。

            只需要一點點手腳,你就可以定義全層次traits。通過這種方式,你只需為一個類層次寫一次traits。全層次traits可以提供很大的靈活性,你可以對類層次中一個特定的類定義特別的traits,也可以對一個子層次定義traits

            全層次traits使用繼承的方法比較怪異。繼承不僅僅是實現運行時多態性的工具,也是編譯時操縱類型的工具。C++把繼承的兩種特性混合在一起,有時候會引起誤解。

            然而,最好的消息是,全層次traits只用了模板的基本功能。就是說,即使你現在使用的編譯器不那么符合標準,你還是可以用這個技術。

            感謝
            非常感謝Herb Sutter, 他花時間審閱了這篇文章并提出深刻的見解。

            posted on 2008-11-09 01:20 肥仔 閱讀(570) 評論(0)  編輯 收藏 引用 所屬分類: C++ 模板

            欧美日韩中文字幕久久伊人| 精品久久久久久综合日本| 一本一道久久精品综合| 久久99精品国产99久久6| 曰曰摸天天摸人人看久久久| 国产成人久久精品二区三区| 99久久精品国产一区二区蜜芽| 99久久精品免费| 77777亚洲午夜久久多人| 久久亚洲av无码精品浪潮| 久久久国产精华液| 久久综合久久鬼色| 97精品伊人久久大香线蕉| 99久久精品国产一区二区| 99久久久精品| 国产精品综合久久第一页| 久久精品国产色蜜蜜麻豆| 久久综合伊人77777麻豆| 免费精品国产日韩热久久| 狠狠88综合久久久久综合网| 伊人久久免费视频| 久久亚洲AV成人无码软件| 久久婷婷激情综合色综合俺也去| a高清免费毛片久久| 久久精品国产亚洲7777| 久久综合给合久久狠狠狠97色69| 亚洲国产精品久久久久久| 77777亚洲午夜久久多喷| 国产精品久久久久乳精品爆| 久久无码AV一区二区三区| 久久精品无码一区二区无码| 久久免费香蕉视频| 久久A级毛片免费观看| 人妻系列无码专区久久五月天| 久久男人Av资源网站无码软件| 久久不见久久见免费影院www日本| 精品多毛少妇人妻AV免费久久 | 久久国产精品成人影院| 日韩十八禁一区二区久久| 久久美女人爽女人爽| 91精品观看91久久久久久 |