《Effective C++》學習筆記——條款27,effectivejava筆記

來源:互聯網
上載者:User

《Effective C++》學習筆記——條款27,effectivejava筆記

***************************************轉載請註明出處:http://blog.csdn.net/lttree********************************************





五、Implementations



 

Rule 27:Minimize casting

規則 27:盡量少做轉型動作




1.一些基礎

C++規則的設計目標之一 —— 保證“類型錯誤”絕對不可能發生。

理論上,如果程式很"乾淨地"通過編譯,就表示它並不企圖在任何對象身上執行任何不安全、無意義、愚蠢荒謬的操作。

But,轉型(cast)破壞了類型系統(type system),這種可能會導致任何種類的麻煩,而且這些麻煩繁瑣度不一。

如果你用的是C、Java 或 C# ,這點就需要特別注意一下,因為那些語言中的轉型比較必要而無法避免,與C++相對比較而言,也不是特別危險。


關於轉型(cast),這裡通常有三種不同的形式:( 都是將expression轉型為T )

—— C風格的轉型動作:  (T)expression

—— 函數風格的轉型動作:  T(expression)

這兩種形式沒有差別,只是小括弧的位置不同而已,可以稱這兩種形式為 " 舊式轉型 " 

C++ 還提供了四種新式轉型(常常被稱為 new-style 或者 C++-style )

—— const_cast<T> ( expression ) 

這種通常用來將對象的常量性轉除。它也是 唯一 有此能力的C++-style轉型操作符。

—— dynamic_cast<T> ( expression )

主要用來執行 ”安全向下轉型 “,也就是用來決定某對象是否歸屬繼承體系中的某個類型。 它是 唯一 無法由舊式文法執行的動作,也是 唯一 可能耗費重大運行成本的轉型動作。

—— reinterpret_cast<T> ( expression )

執行低級轉型,實際動作(及結果)可能取決於編譯器,這也就表示它不可移植。 例如:將一個 pointer to int 轉型為 int。這一類轉型在低級代碼以外很少見。

—— static_cast<T> ( expression )

用來執行強迫隱式轉換,例如:將一個 non-const 對象轉換為 const 對象,或將一個 int 轉為 double等等。它也可以用來執行上述多種轉換的反向轉換,例如:將 void* 指標轉為 typed 指標,將 pointer-to-base 轉為 pointer-t0-derived。但它無法將 const 轉為 non-const,這個只有 const_cast才辦得到。


這麼多種形式的轉型,雖然舊式轉型仍然合法,但新式轉型較受歡迎。原因有二:

> 它們很容易在代碼中被辨識出來(不論是人工辨識或使用工具),因而得以簡化“找出類型系統在哪個地點被破壞”的過程。

> 各轉型動作的目標愈窄化,編譯器愈可能診斷出錯誤的運用。

PS: 有一個 唯一  使用舊式轉型的時機,當需要調用一個 explicit 建構函式將一個對象傳遞給一個函數時。

比如:

class Widget  {public:    explicit Widget( int size );    ...};void doSomeWork( const Widget& w );// 以一個int加上“函數風格”的轉型動作建立一個WidgetdoSomeWork( Widget(15) );// 以一個int加上“C++風格”的轉型動作建立一個WidgetdoSomeWork( static_cast<Widget>(15) );





2.一些東西

① 許多程式員認為,轉型其實什麼都沒做,只是告訴編譯器把某種類型視為另一種類型。But,這是錯誤的觀念。

任何一個類型轉換往往真的令編譯器編譯出運行期間執行的碼。

例如在下面這段程式中:

int x,y;...double d = static_cast<double>(x)/y;    // x除以y,使用浮點數除法

將int轉型為double幾乎肯定會產生一些代碼,因為在大部分計算機體繫結構中,int的底層表述不同於double的底層表述。

但在下面這個例子,尤其需要注意一下:

class Base  { ... };class Derived : public Base  { ... };Derived d;Base* pb = &d;    // 隱喻的將Derived* 轉換為 Base* 

在這裡,不過是建立一個 基類的指標 指向一個 衍生類別的對象,但有時候上述兩個指標值並不相同。這種情況下會有一個 位移量 在運行期被施行於 衍生類別指標身上,用以取得正確的 基類指標值。

上面這個例子已經表明,單一對象(例如一個類型為Derived的對象)可能擁有一個以上的地址(比如 以"Base* 指向 " 和 以" Derived* 指向" 時的地址),在C、Java、C#中都不可能發生這種事,唯獨C++中可以,實際上一旦使用多重繼承,它就一直發生,即使是單一繼承中也可能發生。雖然這裡面可能有些其他的東西,但至少意味著我們應該避免做出"對象在C++中如何如何布局"的假設,更不該以此假設為基礎執行任何轉型動作。

② 我們很容易寫出某些似是而非的代碼(即使是在別的語言中)。

比如,許多應用程式框架都要求 衍生類別內的 虛函數 代碼的第一個動作就先調用基類的對應函數。假設我們有個 Window base class 和一個 SpecialWindow derived class,兩者都定義了 virtual函數 onResize。進一步假設 SpecialWindow 的 onResize 函數被要求收先調用Window的onResize。下面是一種實現方法(似是而非的)

class Window  {    // 基類public:  virtual void onResize()  { ... }  ...};class SpecialWindow : public Window  {    // 衍生類別public:  virtual void onResize()  {    static_cast<Window>(*this).onResize();    // 將*this轉型為Window,然後調用其onResize  ,這樣是錯的    ...  }  ...};

稍微解釋一下,在此代碼中強調了轉型動作(此處用了新式轉型)。這段程式將*this轉型為Window,對函數onResize的調用也因此調用了Window::onResize。但它調用的並不是當前對象的函數,而是稍早轉型動作所建立的一個"*this對象的基類成分"的暫時副本上的onResize。

這個問題的解決方案就是——拿掉轉型動作,直說。

比如,如果只是想調用 基類 版本的onResize函數,令它作用於當前對象身上。所以這麼寫:

class SpecialWindow : public Window  {public:  virtual void onResize()  {    Window::onResize();    // 調用Window::onResize作用於*this身上    ...  }  ...};

在這個例子中,可以發現:如果想用轉型這個動作,這就等於一個warning,因為可能正將局面發展至錯誤的方向上,尤其是當使用dynamic_cast的時候。

③ 對於 dynamic_cast 有些需要注意的地方,它的許多實現版本執行速度相當慢。例如至少有一個很普遍的實現版本基於" class名稱之字串比較 ",如果你在四層深的單繼承體系內的某個對象身上執行 dynamic_cast ,剛才說的那個實現版本所提供的每一次 dynamic_cast 可能會好用多達四次的strcmp調用,用以比較class名稱。深度繼承或多重繼承的成本更高!

什麼時候用 dynamic_cast 呢?用dynamic_cast通常是因為你想在一個認定的 衍生類別 對象身上執行 衍生類別操作函數,但手上卻只有一個 指向基類 的指標或引用。

這裡有兩個做法來避免這個問題

> 使用容器並在其中儲存直接指向衍生類別對象的指標(通常是智能指標),如此便消除了"通過基類介面處理對象"的需要。

用之前Window的例子,如果Window/SpecialWindow繼承體系中只有SpecialWindow才支援閃爍效果,試著 不要 這樣做:

class Window { ... };class SpecialWindow : public Window  {public:  void blink();  ...};typedef std::vector<std::tr1::shared_ptr<Window> > VPW;VPW winPtrs;...for( VPW::iterator iter = winPtrs.begin() ; iter != winPtrs.end() ; ++iter )  {  if( SpecialWindow* psw = dynamic_cast<SpecialWindow* >( iter->get() ) )    psw->blink();}

而應該是這樣:

typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;VPSW winPtrs;...for( VPSW::iterator iter = winPtrs.begin() ; iter != winPtrs.end() ; ++iter )  {  // 沒使用dynamic_cast  (*iter)->blink();}

當然,這種做法讓你無法再同一個容器記憶體儲指標"指向所有可能之各種Window衍生類別"。如果真要處理多種視窗類別型,這就可能需要更多的容器,它們都必須具備型別安全。

> 另一種做法可以 通過基類介面處理"所有可能之各種Window衍生類別",那就是在基類內提供virtual函數做你想對各個Window衍生類別做的事。接著用這個例子,雖然只有SpecialWindow可以閃爍,但或許將閃爍函式宣告於基類內並提供一份"什麼也沒做"的預設實現碼是有意義的:

class Window  {public:  virtual void blink()  { }    // 預設實現代碼  ...};class SpecialWindow : public Window {public:  virtual void blink() { ... };    // 在SpecialWindow類內,blink函數做一些動作  ...}typedef std::vector< std::tr1::shared_ptr<Window> > VPW;    // 內含指標的容器,指向所有可能的Window類型VPW winPtrs;...for( VPW::iterator iter = winPtrs.begin() ; iter != winPtrs.end() ; ++iter )    // 這裡沒使用 dynamic_cast  (iter*)->blink();

不論哪一種寫法——" 使用型別安全容器"或"將virtual函數往繼承體繫上方移動"——都並非放之四海皆準,但在許多情況下它們都提供一個可行的dynamic_cast替代方案。




3.注意

一定要避免一件事——連串 cascading dynamic_casts

也就是,類似這樣的事情:

class Window  { ... };...    // 衍生類別在這裡定義teypdef std::vector<std::tr1::shared_ptr<Window> > VPW;VPW winPtrs;...for( VPW::iterator iter = winPtrs.begin() ; iter != winPtrs.end() ; ++iter )  {  if( SpecialWindow1 * psw1 = dynamic_cast<SpecialWindow1*>(iter->get()) )  { ... }  else if( SpecialWindow2 * psw2 = dynamic_cast<SpecialWindow2*>(iter->get()) )  { ... }  else if( SpecialWindow3 * psw3 = dynamic_cast<SpecialWindow3*>(iter->get()) )  { ... }  ...}

這樣的代碼又大又慢,而且基礎不穩,因為每次Window class繼承體系一有所改變,所有這一類代碼都必須再次檢閱看看是否需要修改。

例如:一旦加入新的衍生類別,或許上述所有的連串判斷中需要加入新的條件分支。這樣的代碼應該被“基於virtual函數調用”的取代。




4.總結

優良的C++代碼很少使用轉型,但若要說完全擺脫它們又不太可能。有很多轉型是通情達理的,雖然有些並不是必須那樣做。如同面對多種的建構函式那樣,我們應該儘可能的少用轉型動作,通常會把它隱藏在某個函數內,函數的介面會保護調用的人不受函數內部的幹擾。

★ 請記住 ☆

如果可以,盡量避免轉型,特別是在注重效率的代碼中避免 dynamic_casts。如果有個設計需要轉型動作,試著發展無需轉型的替代設計。

如果轉型是必要的,試著將它隱藏於某個函數的背後。使用者隨後可以調用該函數,而不需要將轉型放進他們自己的代碼內。

寧可使用C++-style轉型,不要使用舊式轉型。新式轉型容易辨識,並且有著分門別類的支援。






***************************************轉載請註明出處:http://blog.csdn.net/lttree********************************************

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.