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*
.
Wielokrotne dziedziczenie to nie jedyny problem, odpowiedź AndreyT'a rozwiązuje problem niewłaściwego typu downclockingu. –
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. –