轉型(casts)破壞了類型系統(type system)。可能導致任何類型的麻煩。
c++提供四種新式轉型
const_cast<T>(expression) //cast away the constness
dynamic_cast<T>(expression) //safe downcasting安全向下轉型
reinterpret_cast<T>(expression)//意圖執行低級轉型,實際動作(及結果)可能取決於編譯器,這也表示它不可移植;低級代碼以外很少見
static_cast<T>(expression)//用來強迫隱式轉換(implicit conversions)
c風格舊式轉型
(T)expression
T(expression)
新式轉型比較受歡迎,1,很容易在代碼中被辨別出來;2,各種轉型動作愈窄化,編譯器愈能診斷出錯誤的運用。
唯一使用舊式轉型的時機是當調用一個explicit建構函式將一個對象傳遞給一個函數時:
class Widget{
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget &w);
doSomeWork(Widget(15));//函數風格的轉型動作建立一個Widget
doSomeWork(static_cast<Widget>(15));//c++風格的轉型動作建立一個Widget對象
蓄意的“對象產生”動作不怎麼像“轉型”,很可能使用函數風格的轉型動作,而不使用static_cast。但有時最好忽略你的感覺,始終理智的使用新式轉型。
任何一個類型轉換(不論是通過轉型動作而進行的顯示轉換還是通過編譯器完成的隱式轉換)往往真的令編譯器編譯出運行期間執行的碼
int x, y;
...
double d = static_cast<double>(x)/y;//使用浮點數除法
將int 轉換成double幾乎肯定會產生一些碼,因為大部分電腦體繫結構中,int的底層表述不同於double的地層表述。
class Base{...};
class Derived : public Base{...};
Derived d;
Base* pb = &d;//隱喻的將derived*轉換成Base*
這裡我們只是建立一個base class指標指向一個derived class對象,但有時候上述的兩個指標值並不相同。這種情況下會有個位移量在運行期被施行於Derived*指標身上,用於取得正確的Base*指標值。
上述例子表明,單一對象(例如一個類型為Derived的對象)可能擁有一個以上的地址(例如“以Base*指向它”時的地址和以“Derived*指向它”時的地址)。c,java,c#不可能發生這種事,但c++可能!實際上一旦使用多重繼承,這事幾乎一直發生著。即使是在單一繼承中也可能發生。雖然這還有其他意涵,但至少意味著你通常應該避免做出“對象在c++中如何布局”的假設,更不應該以此假設為基礎執行任何轉型動作。例如將對象地址轉型成char*指標,然後在他們身上進行指標運算,幾乎總是導致無意義(不明確)行為。
對象的布局方式和他們的地址計算方式隨編譯器的不同而不同,那意味著“由於知道對象如何布局”而設計的轉型,在某一平台行的通,在其他平台並不一定行得通。
另一件關於轉型的有趣事情是:我們很容易寫出某些似是而非的代碼(其他語言中也許是對的)。例如SpecialWindow的onResize被要求首先調用Window的onResize。下面是看起來對,實際上錯:
class Window{
public:
virtual void onResize(){...}
...
};
class SpecialWinddow:public Window{
public:
virtual void onResize(){
static_cast<Window>(*this).onResize();//將*this轉換成Window,然後調用其onResize;這樣不行!
...
}
};
它調用的並不是當前對象上的函數,而是稍早轉型動作所建立的一個“*this對象的base class成分”的暫時副本身上的onResize!並不是在當前對象身上調用Window::onResize之後又在該對象上執行SpecialWindow專屬行為。不,它是在“當前對象之base calss成分”的副本上調用Window::onResize,然後在當前對象上執行SpecialWindow專屬動作。如果Window::onResize修改了對象內容,當前對象其實沒被改動,改動的是副本。然而SpecialWindow::onResize內如果也修改對象,當前對象真的會被改動。這使當前對象進入一種“傷殘”狀態:其base class成分的更改沒有落實,而derived class成分的更改倒是落實了。
真正的解決方案是:
class SpecialWinddow:public Window{
public:
virtual void onResize(){
Window::onResize();//調用Window::onResize作用於*this身上
...
}
};
dynamic_cast的許多實現版本執行速度相當慢。假如至少有一個很普通的實現版本基於“class名稱之字串比較”,深度繼承或多重繼承的成本更高!某些實現版本這樣做有其原因(它們必須支援動態連結)。在對注重效率的代碼中更應該對dynamic_cast保持機敏猜疑
之所以要用dynamic_cast,通常是因為你想在一個你認定為derived class對象身上執行derived class操作函數,但你的手上只有一個“指向base”的pointer或者reference,你只能靠他們來處理對象。兩個一般性做法可以避免這個問題:
一,使用容器,並在其中儲存直接指向derived class 對象的指標(通常是智能指標),如此便消除了“通過base class介面處理對象”的需要。假設先前的Window/SpecialWindow繼承體系中有SpecialWindows才支援閃爍效果,試著不要這樣做:
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 != vinPtrs.end(); ++iter)
{
if(SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get()))
psw->blink();
}
應該改而這樣做:
typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VSPW;
VSPW winPtrs;
for (VSPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
(*iter)->blink();
}
當然,這種做法無法在同一個容器記憶體儲指標“指向所有可能之各種Window衍生類別”。如果需要處理多種視窗類別型,你可能需要多個容器,他們都必須具備型別安全(type-safe)。
另一種做法讓你通過base class介面處理“所有可能之各種window衍生類別”,那就是在base class 裡提供virtual函數做你想對各個Window衍生類別做的事。雖然只有SpecialWindows可以閃爍,但或許將閃爍函式宣告於base class內並提供一份什麼也不做的預設版本是有意義的:
class Window{
public:
virtual void blink(){}
};
class SpecialWindow:public Window{
public:
virtual void blink(){...};
};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
(*iter)->blink();
}
無論哪一種寫法,並非放之四海皆準,但在許多情況下它們都提供一個可行的dynamic_cast替代方案。當它們有此功效時,你應該欣然擁抱它們。
絕對必須拒絕的是所謂的“連串(cascading)dynamic_casts”:
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
if (SpecialWindow1 * psw1 = dynamic_cast<SpecialWindow1*>(iter->get())){...}
if (SpecialWindow2 * psw1 = dynamic_cast<SpecialWindow2*>(iter->get())){...}
if (SpecialWindow3 * psw1 = dynamic_cast<SpecialWindow3*>(iter->get())){...}
}
這樣的代碼應該總是以某些“基於virtual函數調用”的東西取而代之。
優良的c++代碼很少使用轉型,我們應該儘可能隔離轉型動作,通常是把它隱藏在某個函數內,函數的介面會保護調用者不受函數內部任何骯髒齷齪的動作的影響。