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之間的對象偏移值。