item27 盡量少做轉型動作

2021-06-22 21:34:06 字數 3023 閱讀 5539

scott meyers<>第三版---侯捷譯

c++規則的設計目標之一是,保證「型別錯誤」絕不可能發生。然而,轉型破壞了型別系統。那可能導致任何種類的麻煩,有些容易辨識,有些非常隱晦。在c++中轉型是乙個你會想帶著極大尊重去親近的乙個特性。

c風格的轉型動作(舊式轉型):

(t)expression         //將expression 轉型為t

函式風格轉型動作:

t(expression)      //將expression 轉型為t

c++提供的四種新式轉型(常被稱為new-style或c++-style casts):

const_cast(expression)

dynamic_cast(expression)

reinterpret_cast

(expression)

static_cast(expression)

各有不同的目的:

const_cast通常被用來將物件的常量性轉除(cast away the constness)。它也是唯一有此能力的c++-style轉型操作符。

dynamic_cast注要用來執行「安全向下轉型」(safe downcasting),也就是用來決定某物件是否歸屬繼承體系中的某個型別。它是唯一無法由舊式語法執行的動作,也是唯一可能耗費重大執行成本的轉型動作。dynamic_cast運算子可以

在執行期決定真正的型別。

reinterpret_cast取決於編譯器,也就是表示它不可移植。

static_cast 用來強迫隱式轉換(implicit conversions),如將int轉換為double,將non-const物件轉換為const物件等等。

舊式轉型仍然合法,但新式轉型較受歡迎。原因是:1. 它們很容易在**中被辨識出來(不論是人工識別或使用工具如grep),因而得以簡化「找出型別系統在哪個地點被破壞」的過程。2. 各轉型動作的目標愈窄化,編譯器愈可能診斷出錯誤的運用。舉個例子,如果你打算將常量性去掉,除非使用新式轉型的const_cast否則無法通過編譯。

許多程式設計師相信,轉型其實什麼都沒做,只是告訴編譯器把某種型別視為另一種型別。這是錯誤的觀念。任何一種型別轉換(不論是通過轉型操作而進行的顯式轉換,或通過編譯器完成的隱式轉換)往往真的令編譯器編譯出執行期間指行的碼。

class base;

class derived:public base;

derived d;

base *pb = &d;    //隱喻地將derived* 轉換為base*

這裡我們不過是建立乙個base class指標指向乙個derived class物件,但有時候上述的兩個指標值並不相同。這種情況下會有個偏移量(offset)在執行期被施行於derived*指標身上,用於取得正確的base*指標值。

上個例子表明,單一物件(例如乙個型別為derived的物件可能擁有乙個以上的位址,實際上一旦使用多重繼承,這事幾乎一直發生著。即使在單一繼承中也可能發生。)

這裡我寫個程式解釋下,相關知識可參考,stanley b.lippman<>----侯捷譯,相關章節第三章 data語意學。

class base1;

class base2;

class derived:public base1,public base2;

int main(int argc,char* argv)

執行程式觀察,可發現d 和pb1的位址是一樣的。

pb1 = &d;

//虛擬的c++碼

pb1 = (base1*)((char*)&d) ;

pb2 = &d;

//虛擬的c++碼

pb2 = (base2*)((char*)&d + sizeof(base2));

class window

};class specialwindow:public window

};int main(int argc,char* argv)

一如你所預期的,這段程式將*this轉型為window,對函式onresize的呼叫也因此呼叫了window::onresize。但恐怕你沒有想到的,它呼叫的並不是當前物件上的函式,而是稍早轉型動作所建立的乙個"*this 物件之base class成分"的暫時副本身上的onresize!(譯註:函式就是函式,成員函式只有乙份,"呼叫其那個物件身上的函式"有什麼關係呢?關鍵在於成員函式都有個隱藏的this指標,會因此影響成員函式操作的資料。)在說一次,上述**並非在當前物件身上呼叫window::onresize之後又在該物件身上執行specialwindow專屬動作。不,它是在「當前物件之base class成分」的副本上呼叫window::onresize,然後在當前物件身上執行specialwindow專屬動作。如果window::onresize修改了物件內容(不能說沒有可能性,因為onresize是個non-const成員函式),當前物件其實沒被改動,改動的是副本。然而specialwindow::onresize內如果也修改物件,當前物件真的會被改動。這使當前物件進入一種「傷殘」狀態:其base class成分更改沒有落實,而derived class成分更改倒是落實了。

可寫程式測試:

class window

virtual void onresize()

int getw()

};class specialwindow:public window

void onresize()

int gets()

};int main(int argc,char* argv)};

在**dynamic_cast設計意涵之前,值得注意的是,dynamic_cast的許多實現版本指向速度相當慢。例如至少有乙個很普遍的實現版本基於「class名稱之字串比較」,如果你四層深的單繼承體系內的某個物件身上執行dynamic_cast,剛才說的那個實現版本所提供的每一次dynamic_cast可能會耗用多達四次的strcmp呼叫,用於比較class名稱。深度繼承或多重繼承的成本更高!某些實現版這樣做有原因(它們必須支援動態鏈結)。然而我還是要強調,除了對一般轉型保持機敏與猜疑,更應該在注重效率的**中對dynamic_cast保持機敏與猜疑。

27 盡量少做轉型動作

1 c 是強型別語言,保證型別錯誤不會發生,轉型會破壞型別系統。c語言提供了強制轉型語法t expn 或者t expn c 提供了新式轉型。const cast expn static cast expn dynamic cast expn reinterpret cast expn 強烈建議使用新...

條款27 盡量少做轉型動作

c 中的轉型語法包括舊式轉型和新式轉型。舊式轉換包括 t expression 將expression轉型為t,c風格的轉型動作 t expression 將expression轉型為t,函式風格的轉型動作 新式轉型包括 const cast expression const cast通常被用來將物...

條款27 盡量少做轉型動作

c 的設計目標之一是保證 型別錯誤 絕不會發生。但轉型 cast 破壞了型別系統 type system 舊式轉型,c風格的轉型動作,如下 t tmp 將tmp轉型為t t tmp 同上,函式風格的轉型動作 c 提供四種新式轉型 new style或c style casts 1 const cas...