天天看點

快速了解關于括号運算符、static_cast、dynamic_cast和reinterpret_cast

N年以前,年輕無知的偶寫了一篇很2的貼子:

http://blog.csdn.net/superarhow/article/details/1007875

不知道有沒有誤導讀者。好在從閱讀數量來看應該不會誤導很多人吧。。。

關于這幾個運算符的差別,各個地方的資料已經很多了。這篇文章是希望用比較淺顯易懂的表達方式,寫給希望快速了解它們,以及了解不正确使用它們會帶來什麼後果的讀者們看的。筆者水準有限,如有疏漏之處,還請不吝指正。

首先指出,括号運算符是可以完成所有的轉換的。那麼第一個問題就是:為什麼C++要引入這麼幾個cast?既然括号就已經足夠了?

來看下面這一個例子:

class CItem
{
public:
    void SetOwner(void* o) {
        _owner = o;
    }
public:
    void* _owner;
};


class CBanana
{
};


template<typename ItemClass_>
class CContainer
{
public:
    void AddItem(int index, ItemClass_* i) {
        _items[index] = (CItem*)i;
        ((CItem*)i)->SetOwner(this);
    }
public:
    CItem*  _items[10];
};



int main(int argc, char** argv)
{
    CItem* item = new CItem;
    CContainer<CItem> c;
    c.AddItem(0, item);
    CContainer<CBanana> basket;
    CBanana b;
    basket.AddItem(0, &b);   // <---- Here
    return 0;
}


           

CContainer希望它的子類繼承自CItem,以調用它的SetOwner方法。是以在AddItem而是在中間用了(CItem*)這樣的cast。

第一個對AddItem的調用是沒有問題的。第二個調用,可以看到,CBanana類既不是CItem類的子類,而且也沒有實作SetOwner方法,但是編譯器沒有給出任何警告!運作這段程式将毫無疑問的引起程式crash。

所有的cast運算符,都是為了實作“将X當作Y"的功能。那麼就會有下面兩個問題:

1. 如何将X當作Y?

2. X能不能被當作Y?如果不能,怎麼辦?

例如:如何把一個整數當作一個指針?它們本是無關的東西。隐含的操作是這個整數是指針的位址。這種轉換是reinterpret_cast。即不管原來類型如何,都以它們各自自己的意義解釋,進行轉化。在我們上面這個例子中,括号就相當于完成了一次reinterpret_cast。

關于問題2,我們将上面的例子改為用static_cast,那麼在編譯時,編譯器将會出錯:

 error: invalid static_cast from type 'CBanana*' to type 'CItem*'

這就達到了我們的要求。

dynamic_cast和static_cast類似,更為強大的是,它會在運作時檢查指針的類型,如果類型不一緻,則會傳回NULL。當然它需要程式編譯時有RTTI資訊。

是以簡而言之,static_cast和dynamic_cast都是用于對象類型的轉換。

reinterpret_cast還有什麼危險性呢?再看一個例子:

class CFruit
{
public:
    virtual void drink() {
        printf("%s\n", _juice);
    }
    CFruit() : _juice("tomato ice") {}
private:
    char*  _juice;
};

class CVegetable
{
public:
    virtual void cook() {
        printf("%s\n", _dish);
    }
    CVegetable() : _dish("tomato egg") {}
private:
    char*  _dish;
};

class CTomato : public CFruit, public CVegetable
{
public:
    virtual void eat() {
        drink();
        cook();
    }
};

 


int main(int argc, char** argv)
{
    CTomato* tomato = new CTomato;
    /// 1
    ((CVegetable*)tomato)->cook();
    ((CFruit*)tomato)->drink();


    /// 2
    void* p = tomato;
    ((CVegetable*)p)->cook();
    ((CFruit*)p)->drink();


    /// 3
    p = (CVegetable*)tomato;
    ((CVegetable*)p)->cook();
    ((CFruit*)p)->drink();


    printf("(CTomato*)tomato=%p (CFruit*)tomato=%p (CVegetable*)tomato=%p\n",
            (CTomato*)tomato,   (CFruit*)tomato,   (CVegetable*)tomato);
    return 0;
} 

           

猜猜看第1,2,3段都會列印出什麼?然後再讓最後一句printf來告訴你答案。

第1段會列印出正确結果,而2則兩個都是tomato ice;3則兩個都是tomato egg。

我們這裡的括号也是reinterpret_cast,因為void*不含任何類型資訊。而在一個多重繼承的對象中,CTomato的記憶體組織如下:

+0 [CTomato/CFruit]  +N [CVegetable]

CTomato和CFruit的部份是指向同一個位址,而CVegetable則需要一個偏移。是以,在使用reinterpret_cast時,CTomato和CFruit不會有任何問題,但CVegetable的部份就會出錯了。以上例子隻是出錯,而實際使用中,因為對象布局的不同,通常都是以crash告終。

那麼為什麼1的部份正确呢?因為它是對象間的轉換,有對象資訊,是以編譯器實際上是使用了static_cast來進行的轉換。這個轉換翻譯成彙編語言,也就是加或減去CVegetable和CTomato之間的對象偏移值。

繼續閱讀