Tips: This article based on Scott Meyers's <<Effective C++>> article 27: Minimize Casting
C++規則的設計目標之一,是保證"類型錯誤"絕對不可能發生。理論上你的程序可以很“干淨”的通過編譯,就表示它並不企圖在任何對象身上執行任何不安全的,無意義的,愚蠢荒謬的操作。這是一個極具價值的保證,可別草率的放棄。
但是,轉型(casting)卻破壞了類型系統(type system)。
C++提供了三中不同類型的轉化風格:
- C風格的轉換: (T)expression
- 函數風格的轉換: T(expression)
- 新式風格的轉換(new-style or C++ style casts)
C++提供了4種形式的新式轉換,每種形式的轉換如下:
1)const_cast<T>(expression): const_cast 通常被用來將對象的常量性轉除(cast away the constness)。它也是唯一具有此能力的的C++-style轉型操作。
2)dynamic_cast<T>(expression): dynamic_cast 主要用來執行“安全向下轉型”(Safe downcasting), 也就是用來決定某對象是否歸屬繼承體系中的某個類型。
它是唯一一個無法用舊式語法執行的動作,也是唯一可能消耗重大運行成本的轉型動作!(注:舊式風格的轉換無法實現父類對象到子類對象的轉換)
3) reinterpret_cast<T>(expression): 意圖執行低級轉型,實際動作(及結果)可能取決於編譯器,這就表示它不可移植。這個轉換多用在低級代碼中。
4)static_cast<T>(expression):用來強迫隱式轉換(implicit conversions), 例如將non-const對象轉換為const對象,或將int轉換位double等等。它也可以來
執行上述多種轉換的反向轉換。例如,其可以將void型的指針轉換位typed型的指針,將pointer-to-base 轉換為pointer-to-derived。但它無法將const轉換成no-const,這
只有const_cast才能辦得到!
許多程序員相信,轉型其實什麼都沒有做,只是告訴編譯器將一種類型視為另一種類型,這是錯誤的觀念! 下面我們通過一個實例來驗證轉型期間編譯器確實是做了什麼!
結果:
The Address of base1_pt: 0x7fffc9c64290The Address of base2_pt: 0x7fffc9c64298
The Address of drive_pt: 0x7fffc9c64290
上述的實例表明C++中單一對象(例如上面的Drive對象)可以有一個以上的地址(比如上面Base2*類型的地址和Drive*類型的地址):
由此得到一個重要的結論對象的布局方式和它們的地址計算方式隨著編譯器的不同而不同,那意味著“由於知道對象如何布局”而設計的轉型,
在某一平台上行得通,而在其他平台上不一定行得通。
關於轉型的一個重要事實是你可能因此寫出許多是是而非的代碼,下面就是這樣的一個實例:
1 #include <iostream>
2結果:
The height of the window: 30The width of the window: 20
The color of this window is: 3
static_cast<Window>(*this).onResize();
上面這條語句將*this轉型成Window,對函數onResize的調用因此調用了Window::onResize。但其調用的並不是當前對象上的函數,而是稍早轉型
動作所創建的"*this對象之base class成分"的暫時副本上的OnResize!
將上面的語句改成:
Window::onResize();
結果:
The height of the window: 230The width of the window: 120
The color of this window is: 3
顯然達到了我們想要的效果!
上面這個例子說明了,如果你在程序中遇到了轉型,那麼這活脫脫就是一個警告信號!
tips: 出了要對一般的轉型保持機敏和猜疑,更應該在注重效率的代碼中對dynamic_cast保持機敏和猜疑!
只所以需要dynamic_cast,通常是因為你想在一個你認定為derived class對象身上執行derived class操作函數,但是你的手上只有一個"指向base"的pointer或reference,
你只能靠它們來處理對象!
下面是結局上述問題的一般的做法:
- 使用容器並在其中存儲直接指向derived class對象的指針(通常是智能指針),這樣就消除了"通過base class接口處理對象"的需要。
- 通過base class接口處理"所有可能之各種Window派生類",即在base class內提供virtual函數做你想對各個Window派生類想做的事。
優秀的C++代碼很少使用轉型,但要說完全擺脫它門又是不切實際的。