1. 程式人生 > >條款2:最好使用C++轉型操作符

條款2:最好使用C++轉型操作符

一.舊式的C轉型方式並非是唯一選擇。

它機會允許你將任何型別轉換為任何其他型別,這是十分拙劣的。如果每次轉型都能夠更精確地指明意圖,則更好。

example:將一個pointer-to-const-object轉型為一個pointer-to-const-object(也就是說只是改變物件的常量性),和將一個pointer-to-base-class-object轉型為一個pointer-to-derived-class-object(也就是說完全改變了一個物件的型別),

其間有很大的差異。傳統的C轉型動作對此並無區分。(C語言這種賦值時的型別轉換形式可能會使人感到不精密和不嚴格,因為不管表示式的值怎樣,系統都自動將其轉為賦值運算子左部變數的型別。

● 而轉變後資料可能有所不同,在不加註意時就可能帶來錯誤。 這確實是個缺點,也遭到許多人們批評。但不應忘記的是:c語言最初是為了替代組合語言而設計的,所以型別變換比較隨意。當然, 用強制型別轉換是一個好習慣,這樣,至少從程式上可以看出想幹什麼。

二.舊式轉型的第二個問題是它們難以辨識,新的C++轉型法則十分容易被辨識出來。

舊式轉型的語法結構是由一對小括號加上一個物件名稱(識別符號)組成,而小括號和物件名稱在C++的任何地方都有可能使用。

因此,我們簡直無法回答最基本的轉型相關問題“這個程式中有使用任何轉型動作嗎?”因為人們很可能對轉型動作視而不見,而諸如grep之類的工具又無法區分語法上類似的一些非轉型寫法。

解決C舊式轉型的缺點,C++匯入了4個新的轉型操作符(cast operators):

static_cast,const_cast,dynamic_cast和reinterpret_cast。對大部分使用目的而言,面對這些操作符你唯一需知道的便是,

c++的這個cast 是借用的冶金中鑄造注模的概念,把液態的金屬倒入成型的模範這個過程叫cast。程式設計中把一段資料裝成某個資料型別,讓資料具有某個型別的過程叫做cast。比如讓4個位元組變成一個int型別,把int變成4個char這種過程。基本上和“型別轉換”同義,不過cast在c++語言中是從物件封裝的視角看這個動作。所以有動態cast,靜態cast等多種cast。

過去習慣的寫碼形式應該像如下改寫:

(type) expression    現在應該改為這樣  

static_cast<type>(expression)

2.1static_cast

舉一個例子,假設你想要將一個int轉型為一個double,以強迫一個整數表示式匯出一個浮點數值來。採用C舊式轉型,可以這麼做:

int firstNumber,secondNumber;

...

double result=((double)firstNumber)/secondNumber;

如果採用新的C++轉型法,應該這麼寫:

double result=static_cast<double>(firstNumber)/secondNumber;

這種形式十分容易被辨識出來,不論是對人類或是對工具程式而言。

static_cast基本擁有和C舊式轉型相同的威力與意義,以及相同的限制。

例如,你不能夠利用static_cast將一個struct轉型為int,或將一個double轉型為pointer:這些都是C舊式轉型動作原本就不可以完成的任務。static_cast甚至不能夠移除表示式常量性(constness),因為有一個新式轉型操作符const_cast專司此職。

其他新式C++轉型操作符適用於更集中(範圍更狹窄)的目的。const_cast用來改變表示式中的常量性(constness)或變易性(volatileness)。使用const_cast,便是對人類(以及編譯器)強調,通過這個轉型操作符,你唯一打算改變的是某物的常量性或易變性。這項意願將由編譯器貫徹執行。如果你將const_cast應用於上述以外的用途,那麼轉型動作會被拒絕。

example:

class Widget{...};

class SpecialWidget:public Widget{...};

void update(SpecialWidget *spw);

SpecialWidget sw;      //sw是一個non—const物件,

cosnt SpecialWidget& csw=sw; //csw 確實一個代表sw的reference,

                                                    //並視之為一個const物件。

update(&csw);          //錯誤!不能將const SpecialWidget*

                                  //傳給一個需要SpecialWidget* 的函式。

update(const_cast<SpecialWidget*>(&csw));

                                      //可!&csw的常量性被去除了。也因此,

                                   //csw(亦即sw)在此函式中可以被更改。

update((SpecialWidget*)(&csw));

                                     //情況同上,但使用的是較難辨識

                                    //update()需要的確是SpecialWidget*。

Widget *pw=new SpecialWidget;

update(pw);                 //錯誤!pw的型別是Widget*,但

                                     //update()需要的卻是SpecailWidget*。

update(cosnt_cast<SpecialWidget*>(pw));

                                     //錯誤!cosnt_cast 只能用來影響

                                     //常量性或易變性,無法進行繼承體系

                                     //的向下轉型(cast down)動作。

顯然,const_cast最常見的用途就是將某個物件的常量性去除掉。

2.2dynamic_cast

用來執行繼承體系中“安全的向下轉型或跨系轉型動作”。也就是說你可以利用dynamic_cast,將“指向base class objects的pointers或references”轉型為“指向derived(或sibling base)class objects的pointers或references”,並得知轉型是否成功。如果轉型失敗,會以一個null指標(當轉型物件是指標)或一個exception(當轉型物件是reference)表現出來:

Widget *pw;

...

update(dynamic_cast<SpecialWidget*>(pw));

                                                                       //很好,傳給update()一個指標,指向

                                                                    // pw所指的SpecialWidget——如果pw 

                                                                   // 真的指向這樣的東西;否則傳過去的

                                                                 //將是一個null指標。

void updateViaRef(SpecialWidget& rsw);

updateViaRef(dynamic_cast<SpecialWidget&>(*pw));

                                                                                      //很好,傳給updateViaRef()的是

                                                                                      //pw所指的SpecialWidget——如果

                                                                                       //pw真的指向這樣的東西;否則

                                                                                       //丟擲一個exception。

dynamic_cast只能用來協助你巡航於繼承體系之中。它無法應用在缺乏虛擬函式(條款24)的型別上(通過這找到繼承體系的所以不能少),也不能改變型別的常量性(constness):

int firstNumber,secondNumber;

...

double result=

dynamic_cast<double>(firstNumber)/secondNumber;

                                                          //錯誤!未涉及繼承機制。

cosnt SpecialWidget sw;

...

update(dynamic_cast<SpecailWidget*>(&sw);

                       //錯誤!dynamic_cast不能改變常量性。

如果你想為一個不涉及繼承機制的型別執行轉型動作,可使用static_cast;要改變常量性(constness),則必須使用cosnt_cast。

最後一個轉型操作符是reinterprt_cast。這個操作符的轉換結果幾乎總是與編譯平臺息息相關。所以reinterpret_casts不具有移植性。

reinterpret_cast的常用用途是轉換“函式指標”型別。假設有一個數組,儲存的都是函式指標,有特定的型別:

typedef void(*FuncPtr)()                    //FuncPtr是一個指標,指向某個函式。

                                                             //後者無須任何自變數,返回值為void。

FuncPtr functionPtrArray[10];                  //funcPtrArray是一個數組,

                                                              //內有10個FuncPtrs。

假設由於某種原因,你希望將以下函式的一個指標放進funcPtrArray中:

int doSomething();

如果沒有轉型,不可能辦到這一點,因為doSomething的型別與funcPtrArray所能接受的不同。funcPtrArray內各函式的返回值是void,但doSomething的返回值確是int:

funcPtrArray[0]=&doSomething;     //錯誤!型別不符。

使用reinterpret_cast,可以強迫編譯器瞭解你的意圖。

funcPtrArray[0]=                           //這樣便可以通過編譯。

    reinterpret_cast<FuncPtr>(&doSomething);

函式的指標的轉型動作,並不具有移植性(C++不保證所有的函式指標都能以此方式重新呈現),某些情況下這樣的轉型可能會導致不正確的結果(條款31),所以你應該儘量避免將函式指標轉型,除非萬不得已。

如果你的編譯器尚未支援這些新式轉型動作,你可以使用傳統轉型方式來取代static_cast,const_cast和reinterpret_cast.甚至可以利用巨集(macros)來模擬這些新語法。

      #define static_cast(TYPE,EXPR)    ((TYPE)(EXPR))

      #define const_cast(TYPE,EXPR)    ((TYPE)(EXPR))

      #define reinterpret_cadt(TYPE,EXPR)((TYPE)(EXPR))

    上述新語法的使用方式如下:

double result=static_cast(double,firstNumber)/secondNumber;

update(const_cast(SpecialWidget*,&sw));

funcPtrArray[0]=reinterpret_cast(FuncPtr,&doSomething);

  這些近似法當然不像其本尊那麼安全,但如果你現在就使用它們,一旦你的編譯器開始支援新式轉型,程序升級的過程便可以簡化。(已經不用考慮這方法了,除非老專案)

至於dynamic_cast,沒有什麼簡單方法可以模擬其行為,不過許多程式庫提供了一些函式,用來執行繼承體系下的安全轉型動作。如果你手上沒有這些函式,而卻必須執行這型別轉型,你也可以回頭使用舊式的C轉型語法,但它們不可能告訴你轉型是否成功。當然,你也可以定義一個巨集,看起來像dynamic_cast,就像你為其他轉型操作符所做的那樣:

#define dynamic_cast(TYPE,EXPR)  ((TYPE)(EXPR))

這個近似法並非執行真正的dynamic_cast,所以它無法告訴你轉型是否成功。

文章內容來源《More Effective C++》侯捷作為自己學習筆記之用,難免有錯漏之處,詳細內容請看圖書及C++ reference