2010-03-04 47 views
11

AFAIK,對於指針/引用static_cast,如果類定義此時對編譯器不可見,那麼static_cast的行爲將與reinterpret_cast類似。static_cast safety

爲什麼static_cast對於指針/引用是不安全的並且對數值是安全的?

回答

22

總之,由於多重繼承。

在長:

#include <iostream> 

struct A { int a; }; 
struct B { int b; }; 
struct C : A, B { int c; }; 

int main() { 
    C c; 
    std::cout << "C is at : " << (void*)(&c) << "\n"; 
    std::cout << "B is at : " << (void*)static_cast<B*>(&c) << "\n"; 
    std::cout << "A is at : " << (void*)static_cast<A*>(&c) << "\n"; 

} 

輸出:

C is at : 0x22ccd0 
B is at : 0x22ccd4 
A is at : 0x22ccd0 

注意的是,爲了正確地轉換爲B *的static_cast了改變指針值。如果編譯器沒有C的類定義,那麼它不會知道B是一個基類,它肯定不知道要應用哪個偏移量。

但在這種情況下沒有定義可見,的static_cast不表現得像reinterpret_cast的,它是被禁止:

struct D; 
struct E; 

int main() { 
    E *p1 = 0; 
    D *p2 = static_cast<D*>(p1); // doesn't compile 
    D *p3 = reinterpret_cast<D*>(p1); // compiles, but isn't very useful 
} 

一個普通的C風格的演員,(B*)(&c)確實你說的話:如果結構的定義C是可見的,表明B是基類,那麼它與static_cast相同。如果類型只是前向聲明的,那麼它與reinterpret_cast相同。這是因爲它被設計爲與C兼容,這意味着它必須做C在C中可能做的事情。

static_cast總是知道如何處理內置類型,這實際上是內置的手段。它可以將int轉換爲float,等等。所以這就是爲什麼它對數字類型總是安全的,但是它不能轉換指針,除非(a)它知道它指向的是什麼,(b)指向類型之間有正確的關係。因此它可以將int轉換爲float,但不能將int*轉換成float*

由於AndreyT說,還有一個辦法,你可以使用static_cast不安全,編譯器可能不會救你,因爲代碼是合法的:

A a; 
C *cp = static_cast<C*>(&a); // compiles, undefined behaviour 

的事情之一static_cast能做的就是「向下」指向派生類的指針(在這種情況下,C是A的派生類)。但是,如果說實際上並不是派生類,那麼你就註定了。 A dynamic_cast將在運行時執行檢查,但對於我的示例類C,不能使用dynamic_cast,因爲A沒有虛擬功能。

您也可以使用static_castvoid*進行不安全的事情。

+0

多重繼承是不是唯一的問題,AndreyT的回答解決向下轉換到錯誤類型的問題。 – 2010-03-04 21:44:12

+0

的確,我只完全回答了問題的第一段,部分是第二段。不是static_cast安全性的廣泛問題(標題中)。 – 2010-03-05 11:33:46

5

不,您的「AFAIK」不正確。 static_cast從不表現爲reinterpret_cast(除非可能在您轉換爲void *時,雖然此轉換通常不應由reinterpret_cast執行)。

首先,當static_cast用於指針或引用的轉換,static_cast的說明書中明確要求有一定的關係類型之間存在(並且是已知的static_cast)。對於班級類型,他們通過繼承關聯,如static_cast所感知的。如果兩種類型都沒有完全由static_cast這一點來定義,則無法滿足該要求。所以,如果在static_cast這個點上定義是不可見的,代碼根本就不會編譯。

爲了說明上述與例子:static_cast可以使用的冗餘]來執行對象的指針upcasts。該代碼

Derived *derived = /* whatever */; 
Base *base = static_cast<Base *>(derived); 

只有編譯時,下面的代碼是編譯

Base *base(derived); 

併爲此編譯兩種類型的定義必須是可見的。

另外,static_cast可以被用來執行對象指針向下轉換。該代碼

Base *base = /* whatever */; 
Derived *derived = static_cast<Derived *>(base); 

只有編譯時,下面的代碼是編譯

Base *base(derived); // reverse direction 

,並再次爲這個編譯兩種類型的定義必須是可見的。

所以,你根本就無法使用static_cast未定義類型。如果你的編譯器允許的話,這是編譯器中的一個錯誤。

static_cast可以是不安全的用於一個完全不同的原因的指針/引用。 static_cast可以執行對象指針/引用類型的分層向下轉換,而不檢查對象的實際動態類型。 static_cast也可以爲方法指針類型執行分層上傳。使用這些未經檢查的強制轉換的結果可能導致未定義的行爲,如果不謹慎的話。

其次,當static_cast與算術類型一起使用時,語義是完全不同的,並且 與上面沒有任何關係。它只是執行算術類型轉換。只要它們符合你的意圖,它們總是非常安全(除了範圍問題)。實際上,避免使用static_cast進行算術轉換並使用舊的C風格轉換可能是一種很好的編程風格,只是爲了在始終安全的算術轉換和潛在不安全的分層指針/參考轉換之間的源代碼中提供明顯區別。

+0

「使用舊C_style蒙上而不是」 - 這就是我平時做太多,但後來有人抱怨,我們結束了爭論是否有一個C樣式轉換,他們說是邪惡的,和單參數的構造函數,他們」之間有什麼區別對於;-) – 2010-03-04 19:58:00

+0

非常滿意,因此,如果static_cast未針對未定義的指針/引用進行編譯,那麼對於該類型的轉換,它可以被認爲是安全的? – dimba 2010-03-04 20:22:44

+0

@idimba:呃...我很難嘗試將「安全」和「不安全」的概念應用於無法編譯的代碼。我甚至會說這沒什麼意義。 – AnT 2010-03-04 20:24:38