《Effective C++ 中文版 第三版》讀書筆記
** 條款 27:盡量少做轉(zhuǎn)型動作 **
轉(zhuǎn)型(casts)破壞了類型系統(tǒng)(type system)。可能導致任何關(guān)于類型的麻煩。
C++ 提供四種新式轉(zhuǎn)型:
const_cast<T>(expression) // cast away the constness
dynamic_cast<T>(expression) // safe downcasting 安全向下轉(zhuǎn)型
reinterpret_cast<T>(expression) // 意圖執(zhí)行低級轉(zhuǎn)型,實際動作(及結(jié)果)可能取決于編譯器,這也表示它不可移植;低級代碼以外很少見
static_cast<T>(expression) // 用來強迫隱式轉(zhuǎn)換(implicit conversions)
1.const_cast 通常被用來將對象的常量性轉(zhuǎn)除(cast away the constness)。他也是唯一具有此能力的 C++ style 轉(zhuǎn)型操作符。
2.dynamic_cast 主要用來執(zhí)行“安全向下轉(zhuǎn)型”(safe downcasting),也就是用來決定某對象是否歸屬集成體系中的某個類型。它是唯一無法由舊式語法執(zhí)行的動作,也是唯一可能耗費重大運行成本的轉(zhuǎn)型動作。
3.reinterpret_cast 意圖執(zhí)行低級轉(zhuǎn)型,實際動作(及結(jié)果)可能取決于編譯器,這也就表示它不可移植。
4.static_cast 用來強迫隱式轉(zhuǎn)換,例如將 non-const 對象轉(zhuǎn)型為 const 對象,或?qū)?int 轉(zhuǎn)型為 double 等等。他也可以用來執(zhí)行上述多種轉(zhuǎn)換的反向轉(zhuǎn)換,例如將 void* 指針轉(zhuǎn)換為 typed 指針,將 pointer-to-base 轉(zhuǎn)為 pointer-to-derived。但它無法將 const 轉(zhuǎn)為 non-const(這個只有 const_cast 能辦到)。
C 風格舊式轉(zhuǎn)型:1. (T)expression 2. T(expression)
新式轉(zhuǎn)型比較受歡迎:1.很容易在代碼中被辨別出來;2.各種轉(zhuǎn)型動作愈窄化,編譯器愈能診斷出錯誤的運用。
唯一使用舊式轉(zhuǎn)型的時機是當調(diào)用一個 explicit 構(gòu)造函數(shù)將一個對象傳遞給一個函數(shù)時:
class Widget{
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget &w);
doSomeWork(Widget(15)); // 函數(shù)風格的轉(zhuǎn)型動作創(chuàng)建一個 Widget
doSomeWork(static_cast<Widget>(15)); // C++ 風格的轉(zhuǎn)型動作創(chuàng)建一個 Widget 對象
蓄意的 “對象生成” 動作不怎么像 “轉(zhuǎn)型”,很可能使用函數(shù)風格的轉(zhuǎn)型動作,而不使用 static_cast。但有時最好忽略你的感覺,始終理智的使用新式轉(zhuǎn)型。
任何一個類型轉(zhuǎn)換(不論是通過轉(zhuǎn)型動作而進行的顯示轉(zhuǎn)換還是通過編譯器完成的隱式轉(zhuǎn)換)往往真的令編譯器編譯出運行期間執(zhí)行的碼。例如在這段程序中:
int x, y;
...
double d = static_cast<double>(x)/y; // 使用浮點數(shù)除法
將 int 轉(zhuǎn)換成 double 幾乎肯定會產(chǎn)生一些碼,因為大部分計算機體系結(jié)構(gòu)中,int 的底層表述不同于 double 的地層表述。下面這個例子可能讓你稍微瞪大眼睛:
class Base{...};
class Derived : public Base{...};
Derived d;
Base* pb = &d; // 隱喻地將 derived* 轉(zhuǎn)換成 Base*
這里我們只是建立一個 base class 指針指向一個 derived class 對象,但有時候上述的兩個指針值并不相同。這種情況下會有個偏移量在運行期被施行于 Derived* 指針身上,用于取得正確的 Base* 指針值。
上述例子表明,單一對象(例如一個類型為 Derived 的對象)可能擁有一個以上的地址(例如“以 Base* 指向它”時的地址和以“ Derived* 指向它”時的地址)。C,java,C# 不可能發(fā)生這種事,但 C++ 可能!實際上一旦使用多重繼承,這事幾乎一直發(fā)生著。即使是在單一繼承中也可能發(fā)生。雖然這還有其他意涵,但至少意味著你通常應該避免做出“對象在 C++ 中如何布局”的假設(shè),更不應該以此假設(shè)為基礎(chǔ)執(zhí)行任何轉(zhuǎn)型動作。例如將對象地址轉(zhuǎn)型成 char* 指針,然后在他們身上進行指針運算,幾乎總是導致無意義(不明確)行為。
對象的布局方式和他們的地址計算方式隨編譯器的不同而不同,那意味著“由于知道對象如何布局”而設(shè)計的轉(zhuǎn)型,在某一平臺行的通,在其他平臺并不一定行得通。
另一件關(guān)于轉(zhuǎn)型的有趣事情是:我們很容易寫出某些似是而非的代碼(其他語言中也許是對的)。例如 SpecialWindow 的 onResize 被要求首先調(diào)用 Window 的 onResize。下面是看起來對,實際上錯:
class Window{
public:
virtual void onResize(){...}
...
};
class SpecialWinddow:public Window{
public:
virtual void onResize(){
static_cast<Window>(*this).onResize(); // 將*this轉(zhuǎn)換成Window,然后調(diào)用其onResize;這樣不行!
...
}
};
它調(diào)用的并不是當前對象上的函數(shù),而是稍早轉(zhuǎn)型動作所建立的一個 “*this 對象的 base class 成分” 的暫時副本身上的 onResize!并不是在當前對象身上調(diào)用 Window::onResize 之后又在該對象上執(zhí)行 SpecialWindow 專屬行為。不,它是在 “當前對象之 base calss 成分” 的副本上調(diào)用 Window::onResize,然后在當前對象上執(zhí)行 SpecialWindow 專屬動作。如果 Window::onResize 修改了對象內(nèi)容,當前對象其實沒被改動,改動的是副本。然而 SpecialWindow::onResize 內(nèi)如果也修改對象,當前對象真的會被改動。這使當前對象進入一種“傷殘”狀態(tài):其 base class 成分的更改沒有落實,而 derived class 成分的更改倒是落實了。
解決之道是拿掉轉(zhuǎn)型動作,代之你真正想要說的話。所以,真正的解決方法是:
class SpecialWinddow:public Window{
public:
virtual void onResize(){
Window::onResize(); // 調(diào)用 Window::onResize 作用于 *this 身上
...
}
};
在探究 dynamic_cast 設(shè)計意涵之前,值得注意的是,dynamic_cast 的許多實現(xiàn)版本執(zhí)行速度相當慢。假如至少有一個很普通的實現(xiàn)版本基于 “class 名稱之字符串比較”,如果你在四層深的單繼承體系內(nèi)的某個對象身上執(zhí)行 dynamic_cast,可能會耗用多達四次的 strcmp 調(diào)用,用以比較 class 名稱。深度繼承或多重繼承的成本更高!某些實現(xiàn)版本這樣做有其原因(它們必須支持動態(tài)鏈接)。在對注重效率的代碼中更應該謹慎地使用 dynamic_cast。
之所以需要用 dynamic_cast,通常是因為我們想在一個我們認定為 derived class 對象身上執(zhí)行 derived class 操作函數(shù),但我們的手上只有一個“指向 base”的 pointer 或者 reference,你只能靠他們來處理對象。兩個一般性做法可以避免這個問題:
一,使用容器,并在其中存儲直接指向 derived class 對象的指針(通常是智能指針),如此便消除了 “通過 base class 接口處理對象” 的需要。假設(shè)先前的 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();
}
當然,這種做法無法在同一個容器內(nèi)存儲指針 “指向所有可能之各種 Window 派生類”。如果需要處理多種窗口類型,你可能需要多個容器,他們都必須具備類型安全性(type-safe)。
另一種做法讓你通過 base class 接口處理 “所有可能之各種 window 派生類”,那就是在 base class 里提供 virtual 函數(shù)做你想對各個 Window 派生類做的事。雖然只有 SpecialWindows 可以閃爍,但或許將閃爍函數(shù)聲明于 base class 內(nèi)并提供一份什么也不做的缺省版本是有意義的:
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())){...}
else if (SpecialWindow2 * psw1 = dynamic_cast<SpecialWindow2*>(iter->get())){...}
else if (SpecialWindow3 * psw1 = dynamic_cast<SpecialWindow3*>(iter->get())){...}
...
}
這樣產(chǎn)生出來的代碼又大又慢,而且基礎(chǔ)不穩(wěn),因為每次 Window class 集成體系一有改變,所有這一類代碼都必須再次檢閱看看是否需要修改。例如一旦加入新的 derived class,或許上述連串判斷中需要加入新的條件分支。這樣的代碼應該總是以某些 “基于 virtual 函數(shù)調(diào)用” 的東西取而代之。
優(yōu)良的 C++ 代碼很少使用轉(zhuǎn)型,我們應該盡可能隔離轉(zhuǎn)型動作,通常是把它隱藏在某個函數(shù)內(nèi),函數(shù)的接口會保護調(diào)用者不受函數(shù)內(nèi)部任何骯臟齷齪的動作的影響。
請記?。?/p>
- 如果可以,盡量避免轉(zhuǎn)型,特別是在注重效率的代碼中避免 dynamic_cast。如果有個設(shè)計需要轉(zhuǎn)型動作,試著發(fā)展無需轉(zhuǎn)型的替代設(shè)計。
- 如果轉(zhuǎn)型是必要的,試著將它隱藏于某個函數(shù)背后??蛻綦S后可以調(diào)用該函數(shù),而不需要將轉(zhuǎn)型放進他們自己的代碼內(nèi)。
- 寧可使用 C++ style(新式)轉(zhuǎn)型,不要使用舊式轉(zhuǎn)型。前者很容易辨認出來,而且也有著比較分門別類的職責。