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

            洛譯小筑

            別來無恙,我的老友…
            隨筆 - 45, 文章 - 0, 評論 - 172, 引用 - 0
            數據加載中……

            [ECPP讀書筆記 條目37] 避免對函數中繼承得來的默認參數值進行重定義

            讓我們開門見山的討論本話題:可以繼承的函數可以分為兩種:虛擬的和非虛擬的。然而,由于重定義一個派生的非虛函數始終是一個錯誤(參見條目36),因此我們可以放心地將此處的討論范圍縮小至以下情況:繼承一個含有默認參數值的函數。

            此情況下,證明本條目的結論非常簡單:虛函數是動態綁定的,而默認參數值是靜態綁定的。

            你說啥?靜態綁定與動態綁定之間的區別已經讓你頭暈目眩了?(眾所周知,靜態綁定又稱早期綁定,動態綁定又稱晚期綁定。)我們只好復習一下了。

            一個對象的靜態類型就是你在對其進行聲明時賦予它的類型。請考慮下面的類層次結構: 

            // 幾何形狀類

            class Shape {

            public:

              enum ShapeColor { Red, Green, Blue };

             

              // 所有形狀必須提供一個自我繪制函數
              virtual void draw(ShapeColor color = Red) const = 0;

              ...

            };

             

            class Rectangle: public Shape {

            public:

              // 請注意:默認參數值變了——糟糕!

              virtual void draw(ShapeColor color = Green) const;

              ...

            };

             

            class Circle: public Shape {

            public:

              virtual void draw(ShapeColor color) const;

              ...

            };

            用UML來表示:


            現在請考慮下面的指針:

            Shape *ps;                        // 靜態類型 = Shape*

            Shape *pc = new Circle;           // 靜態類型 = Shape*

            Shape *pr = new Rectangle;        // 靜態類型 = Shape*

            示例中,由于pspc以及pr都聲明為指向Shape的指針,因此他們的靜態類型均為Shape*。請注意,這樣做使得無論他們實際指向的對象是什么類型,他們的靜態類型都必為Shape*

            對象的動態類型是通過他當前引用的對象的類型決定的。也就是說,動態類型表明了他應具有怎樣的行為。在上文的示例中,pc的動態類型是Circle*pr的動態類型是Rectangle*。而對于ps來說,他在當前根本不具備動態類型,因為它(目前)還沒有引用任何對象呢。

            動態類型,顧名思義,在程序運行時可能會有所改變,通常是通過賦值操作發生: 

            ps = pc;                           // ps動態類型變為Circle*

            ps = pr;                           // ps動態類型變為Rectangle*

            虛函數是動態綁定的,這就意味著,對于一個特定的函數調用,其調用對象的動態類型將決定調用這一函數的哪個版本: 

            pc->draw(Shape::Red);              // 調用 Circle::draw(Shape::Red)

            pr->draw(Shape::Red);              // 調用 Rectangle::draw(Shape::Red)

            我知道這些都是老生常談了,你當然已經對虛函數有了透徹的理解。只有在虛函數包含默認參數值時,情況才有所不同。這是因為(如上文所述),虛函數是動態綁定的,但是默認參數是靜態綁定的。這也就意味著對于一個虛函數,你可能會調用它在派生類中的定義,而默認參數值則采用基類中的值: 

            pr->draw();                   // 調用 Rectangle::draw(Shape::Red)!

            這種情況下,由于pr的動態類型是Rectangle*,于是此處便調用了虛函數drawRectangle版本,正如你所愿。在Rectangle::draw中,默認參數值是Green。然而,因為pr的靜態類型是Shape*,這里的draw調用將采用Shape類中的默認參數值,而不是Rectangle!最終,在Shape類和Rectangle類之間,對于draw的調用必將出現混亂的無法預知的現象。

            雖然pspcpr是指針,但是并不影響上文的結論。如果它們是引用的話,問題同樣存在。這里只有一個重點:draw是虛函數,他的一個默認參數值在派生類中被重定義了。

            是什么讓C++在處理這一問題時如此不合常理? 答案是:運行時效率。如果默認參數值是動態綁定的話,那么編譯器必須提供一整套方案,為運行時的虛函數參數確定恰當的默認值。而這樣做,比起C++當前使用的編譯時決定機制而言,將會更復雜、更慢。魚和熊掌不可兼得,C++將設計的中心傾向了速度和簡潔,你在享受效率的快感的同時,如果你忽略本條目的建議,你就會陷入困惑。

            一切看上去似乎盡善盡美了,但是一旦你不假思索的遵守本條建議,為基類和派生類分別提供默認參數值的話,看看將會發生什么: 

            class Shape {

            public:

              enum ShapeColor { Red, Green, Blue };

             

              virtual void draw(ShapeColor color = Red) const = 0;

              ...

            };

            class Rectangle: public Shape {

            public:

              virtual void draw(ShapeColor color = Red) const;

              ...

            };

             吁……惱人的重復代碼。還有更糟的:這些重復代碼彼此還有依賴:如果Shape中的默認參數值改變了的話,那么所有的派生類中相應的值都必須改變。否則這些函數仍將改變繼承來的默認參數值。那么怎么辦呢?

            遇到麻煩了?虛函數無法按照你預想的方式運行?這時候明智的做法是:考慮一個替代的設計方案,條目35中介紹了幾種虛函數的替代方案。其中一種是非虛擬接口慣用法方案(NVI慣用法):在基類中用一個公有的非虛函數調用一個私有的虛函數,并在派生類中重定義這一虛函數。在這里,我們將默認參數置于非虛函數中,讓虛函數做具體的工作。

            class Shape {

            public:

              enum ShapeColor { Red, Green, Blue };

             

              void draw(ShapeColor color = Red) const

              {                                // 現在draw是非虛函數

            doDraw(color);                 // 調用一個虛函數

              }

             ... 

            private:

              virtual void doDraw(ShapeColor color) const = 0;

                                               // 這個函數做真正的工作

            };

             

            class Rectangle: public Shape {

            public:

              ...

            private:
              virtual void doDraw(ShapeColor color) const; 

                                               //此處不需要默認參數值
              ...

            };

             由于在派生類中不能對非虛函數進行重載(參見條目36),因此,顯然地,這一設計方案使得draw函數中color參數的默認值永遠為Red


            時刻牢記

            避免在對函數中繼承得來的默認參數值進行重定義,這是因為默認參數值是靜態綁定的,而虛函數(派生類中唯一的一系列可以重定義的函數)是動態綁定的。

            posted on 2012-05-20 11:21 ★ROY★ 閱讀(2106) 評論(0)  編輯 收藏 引用 所屬分類: Effective C++

            久久精品中文无码资源站| 久久久久亚洲av成人网人人软件| 久久亚洲AV无码精品色午夜麻豆| 色综合久久无码五十路人妻| 久久国产精品99精品国产| 久久精品国产99国产精偷| 久久久久无码中| 国产精品美女久久久m| 久久综合鬼色88久久精品综合自在自线噜噜 | 久久精品无码一区二区三区日韩| 久久久久波多野结衣高潮| 青青国产成人久久91网| 伊人久久综合无码成人网| 久久久99精品成人片中文字幕| 国产亚洲精久久久久久无码| 精品国产婷婷久久久| AV狠狠色丁香婷婷综合久久| 性做久久久久久免费观看| 久久精品国产亚洲av高清漫画| 久久久久亚洲av成人网人人软件 | 国内精品伊人久久久久影院对白| yy6080久久| 日韩精品无码久久一区二区三| 久久―日本道色综合久久| 欧美一区二区三区久久综| 欧洲性大片xxxxx久久久| 夜夜亚洲天天久久| 久久综合丝袜日本网| 久久精品草草草| 欧美亚洲另类久久综合| 久久精品水蜜桃av综合天堂| 97久久国产露脸精品国产| 天天做夜夜做久久做狠狠| 美女久久久久久| 狠狠综合久久AV一区二区三区| 中文无码久久精品| 蜜桃麻豆WWW久久囤产精品| 久久99精品国产麻豆| 久久精品蜜芽亚洲国产AV| 久久99精品国产麻豆| 久久免费线看线看|