2010-03-04 13 views
11

AFAIK, dla wskaźników/odnośników static_cast, jeśli definicja klasy nie jest widoczna dla kompilatora w tym momencie, wtedy static_cast będzie zachowywać się jak reinterpret_cast.static_cast bezpieczeństwo

Dlaczego static_cast jest niebezpieczny dla wskaźników/odnośników i jest bezpieczny dla wartości numerycznych?

Odpowiedz

22

W skrócie, z powodu wielokrotnego dziedziczenia.

W długi:

#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"; 

} 

wyjściowa:

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

Należy zauważyć, że w celu przekształcenia prawidłowo B *, static_cast musi zmienić wartość wskaźnika. Gdyby kompilator nie miał definicji klasy dla C, to nie wiedziałby, że B jest klasą bazową, a na pewno nie wiedziałby, jakie przesunięcie zastosować.

Ale w tej sytuacji, w której nie ma definicji jest widoczny, static_cast nie zachowuje się jak reinterpret_cast, jest zabronione:

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 
} 

zwykły C-style cast, (B*)(&c) robi to, co można powiedzieć: jeśli definicja struktury C jest widoczne, pokazując, że B jest klasą podstawową, a następnie jest takie samo jak static_cast. Jeśli typy są zadeklarowane tylko do przodu, to jest to samo co reinterpret_cast. Dzieje się tak dlatego, że jest zaprojektowany do kompatybilności z C, co oznacza, że ​​musi robić to, co robi C w przypadkach, które są możliwe w C.

static_cast zawsze wie, co zrobić dla wbudowanych typów, to jest to, co jest wbudowane znaczy. Może przekonwertować int na float i tak dalej. Dlatego zawsze jest bezpieczny dla typów numerycznych, ale nie może konwertować wskaźników, chyba że (a) wie, na co wskazują, oraz (b) istnieje odpowiedni rodzaj relacji między wskazanymi typami. W związku z tym może konwertować int na float, ale nie int* na float*.

Jak mówi AndreyT, istnieje sposób, że można użyć static_cast niebezpieczny, a kompilator prawdopodobnie nie zbawi cię, ponieważ kod jest legalne:

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

Jedną z rzeczy, które może zrobić, to static_cast "downcast" wskaźnik do klasy pochodnej (w tym przypadku C jest pochodną klasą A). Ale jeśli referand nie jest faktycznie klasą pochodną, ​​jesteś skazany na zagładę. A dynamic_cast wykona sprawdzenie w środowisku wykonawczym, ale dla mojej przykładowej klasy C nie można użyć wartości dynamic_cast, ponieważ A nie ma funkcji wirtualnych.

Można podobnie robić niebezpieczne rzeczy z static_cast do iz void*.

+0

Wielokrotne dziedziczenie to nie jedyny problem, odpowiedź AndreyT'a rozwiązuje problem niewłaściwego typu downclockingu. –

+0

To prawda, odpowiedziałem tylko na pierwszy akapit pytania, a częściowo na drugi akapit. Nie jest to szeroka kwestia (w tytule) bezpieczeństwa static_cast. –

5

Nie, Twoje "AFAIK" jest nieprawidłowe. static_cast nigdy nie zachowuje się tak, jak reinterpret_cast (z wyjątkiem, być może, po konwersji na void *, chociaż ta konwersja zwykle nie powinna być wykonywana przez reinterpret_cast).

Po pierwsze, gdy static_cast służy do wskazywania i odniesienia konwersji specyfikacja static_cast wyraźnie wymaga pewnej relacji między występującymi rodzajami (i będą znane static_cast).W przypadku typów klas, są one powiązane dziedziczeniem, co jest postrzegane jako static_cast. Nie jest możliwe spełnienie tego wymogu bez obiema rodzajami całkowicie zdefiniowanymi przez punkt static_cast. Tak więc, jeśli definicja (definicje) jest (są) niewidoczna w punkcie static_cast, kod po prostu się nie skompiluje.

Aby zilustrować powyższe przykłady: static_cast można użyć [redundantly], aby wykonać upcasty wskaźnika obiektu. Kod

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

jest tylko compilable gdy następujący kod jest compilable

Base *base(derived); 

a do tego skompilować definicja obu typów muszą być widoczne.

Można również użyć static_cast do wykonywania downcastów wskaźnika obiektu. Kod

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

tylko compilable gdy ten kod jest compilable

Base *base(derived); // reverse direction 

i, ponownie, w tym celu opracować definicja dwóch rodzajów być widoczne.

Po prostu nie będzie można używać static_cast z niezdefiniowanymi typami. Jeśli twój kompilator na to pozwala, jest to błąd w twoim kompilatorze.

static_cast może być niebezpieczny dla wskaźników/referencji z zupełnie innego powodu. static_cast potrafi wykonywać hierarchiczne downcasty dla typów/typów obiektów bez sprawdzania rzeczywistego typu dynamicznego obiektu. static_cast może również wykonywać hierarchiczne upcasty dla typów wskaźników metod. Korzystanie z wyników tych niezaznaczonych rzutów może prowadzić do niezdefiniowanych zachowań, jeśli zostanie to zrobione bez zachowania ostrożności.

Po drugie, gdy static_cast jest używany z typami arytmetycznymi, semantyka jest zupełnie inna, a nie ma nic wspólnego z powyższym. Po prostu wykonuje konwersje typu arytmetycznego. Są zawsze całkowicie bezpieczne (poza kwestiami zasięgu), o ile pasują do twojego zamiaru. W rzeczywistości może to być dobry styl programowania, aby uniknąć konwersji static_cast dla konwersji arytmetycznych i zamiast tego używać starych rzutów w stylu C, aby zapewnić wyraźne zróżnicowanie kodu źródłowego między zawsze bezpiecznymi rzutami arytmetycznymi a potencjalnie niebezpiecznymi hierarchicznymi rzutowaniami wskaźnika/odniesienia .

+0

"używaj zamiast tego starych C_style" - to zwykle robię, ale potem ktoś narzeka i kończymy kłócąc się, czy jest jakaś różnica między obsadą w stylu C, która według nich jest zła, a konstruktorem jednoargumentowym, że " jest całkowicie zadowolony z ;-) –

+0

, więc jeśli static_cast nie kompiluje dla niezdefiniowanych wskaźników/odniesień, może być uważane za bezpieczne dla tego typu konwersji? – dimba

+0

@idimba: Er ... Ciężko mi jest próbować zastosować pojęcia "bezpieczne" i "niebezpieczne" do kodu, który się nie kompiluje. Powiedziałbym nawet, że to nie ma sensu. – AnT