C++箴言:將強制轉(zhuǎn)型減到最少

字號:

C++ 的規(guī)則設(shè)計為保證不會發(fā)生類型錯誤。在理論上,如果你的程序想順利地通過編譯,你就不應(yīng)該試圖對任何對象做任何不安全的或無意義的操作。這是一個非常有價值的保證,你不應(yīng)該輕易地放棄它。
    不幸的是,強制轉(zhuǎn)型破壞了類型系統(tǒng)。它會引起各種各樣的麻煩,其中一些容易被察覺,另一些則格外地微妙。如果你從 C,Java,或 C# 轉(zhuǎn)到 C++,請一定注意,因為強制轉(zhuǎn)型在那些語言中比在 C++ 中更有必要,危險也更少。但是 C++ 不是 C,也不是 Java,也不是 C#。在這一語言中,強制轉(zhuǎn)型是一個你必須全神貫注才可以靠近的特性。
    我們就從回顧強制轉(zhuǎn)型的語法開始,因為對于同樣的強制轉(zhuǎn)型通常有三種不同的寫法。C 風格(C-style)強制轉(zhuǎn)型如下:
    (T) expression // cast expression to be of type T
    函數(shù)風格(Function-style)強制轉(zhuǎn)型使用這樣的語法:
    T(expression) // cast expression to be of type T
    這兩種形式之間沒有本質(zhì)上的不同,它純粹就是一個把括號放在哪的問題。我把這兩種形式稱為舊風格(old-style)的強制轉(zhuǎn)型。
    C++ 同時提供了四種新的強制轉(zhuǎn)型形式(通常稱為新風格的或 C++ 風格的強制轉(zhuǎn)型):
    const_cast(expression)
    dynamic_cast(expression)
    reinterpret_cast(expression)
    static_cast(expression)
    每一種適用于特定的目的:
    ·const_cast 一般用于強制消除對象的常量性。它是能做到這一點的 C++ 風格的強制轉(zhuǎn)型。
    ·dynamic_cast 主要用于執(zhí)行“安全的向下轉(zhuǎn)型(safe downcasting)”,也就是說,要確定一個對象是否是一個繼承體系中的一個特定類型。它是不能用舊風格語法執(zhí)行的強制轉(zhuǎn)型。也是可能有重大運行時代價的強制轉(zhuǎn)型。(過一會兒我再提供細節(jié)。)
    ·reinterpret_cast 是特意用于底層的強制轉(zhuǎn)型,導致實現(xiàn)依賴(implementation-dependent)(就是說,不可移植)的結(jié)果,例如,將一個指針轉(zhuǎn)型為一個整數(shù)。這樣的強制轉(zhuǎn)型在底層代碼以外應(yīng)該極為罕見。在本書中我只用了一次,而且還僅僅是在討論你應(yīng)該如何為*內(nèi)存(raw memory)寫一個調(diào)諧分配者(debugging allocator)的時候。
    ·static_cast 可以被用于強制隱型轉(zhuǎn)換(例如,non-const 對象轉(zhuǎn)型為 const 對象(就像 Item 3 中的),int 轉(zhuǎn)型為 double,等等)。它還可以用于很多這樣的轉(zhuǎn)換的反向轉(zhuǎn)換(例如,void* 指針轉(zhuǎn)型為有類型指針,基類指針轉(zhuǎn)型為派生類指針),但是它不能將一個 const 對象轉(zhuǎn)型為 non-const 對象。(只有 const_cast 能做到。)
    舊風格的強制轉(zhuǎn)型依然合法,但是新的形式更可取。首先,在代碼中它們更容易識別(無論是人還是像 grep 這樣的工具都是如此),這樣就簡化了在代碼中尋找類型系統(tǒng)被破壞的地方的過程。第二,更精確地指定每一個強制轉(zhuǎn)型的目的,使得編譯器診斷使用錯誤成為可能。例如,如果你試圖使用一個 const_cast 以外的新風格強制轉(zhuǎn)型來消除常量性,你的代碼將無法編譯。
    當我要調(diào)用一個 explicit 構(gòu)造函數(shù)用來傳遞一個對象給一個函數(shù)的時候,大概就是我僅有的使用舊風格的強制轉(zhuǎn)換的時候。例如:
    class Widget {
    public:
    explicit Widget(int size);
    ...
    };
    void doSomeWork(const Widget w);
    doSomeWork(Widget(15)); // create Widget from int
    // with function-style cast
    doSomeWork(static_cast(15)); // create Widget from int
    // with C++-style cast