Odwieczny dylemat Programisty C++ – zobacz, kiedy użyć jakiego operatora rzutowania.
1. static_cast
Najbardziej przydatny, używaj do:
- rzutowania kompatybilnych typów i wskaźników, np. double -> int, long -> char, int* -> void*
Przykład:
int main() { const double PI = 3.14159265358979323846264279502; int integer_pi = static_cast<int>(PI); . . . return 0; }
2. dynamic_cast
Używaj do rzutowania wskaźników bazowych na pochodne (w dół hierarchii dziedziczenia), gdy nie jesteś pewien kompatybilności typów (czyli że takie rzutowanie rzeczywiście ma sens). W razie niekompatybilności:
- zwróci wartość NULL, w przypadku rzutowania wskaźników
- rzuci wyjątek std::bad_cast, w przypadku rzutowania referencji
Możesz w ten sposób sprawdzić, czy obiekt należy do danej klasy.
Przykład:
class Car {}; class Honda : public Car {}; int main() { Car *car = new Honda(); Honda *honda = dynamic_cast<Honda*>(car); . . . delete honda; return 0; }
3. reinterpret_cast
Używaj do:
- rzutowania kompletnie niepowiązanych typów, np. char* -> long. Zwiększa to podatność aplikacji na błędy, bo kompilator nie ostrzeże Cię, gdy zrobisz nawet całkowicie bezsensowne rzutowanie typu książka -> jabłko. Używaj ostrożnie.
Przykład:
int main() { const char *str = "lalala"; long int str_addr = reinterpret_cast<long int>(str); cout << "Napis " << str << " znajduje sie pod adresem " << str_addr; return 0; }
4. const_cast
Używaj do:
- rzutowania stałych na zmienne i zmiennych na stałe: const T -> T lub T -> const T
- rzutowania volatile T -> T lub T -> volatile T
Przydatne, gdy jakas funkcja oczekuje inaczej zmodyfikowanego parametru, niż posiadamy, np. chce char*, a my mamy const char*. Często sygnalizuje on źle zaprojektowany fragment kodu. Jeśli musisz go użyć – prawdopodobnnie coś w kodzie nie zostało do końca przemyślane ;)
Przykład:
void write(char *s) { cout << s; } int main() { const char *str = "lalala"; write(const_cast<char*>(str)); return 0; }
PS. Tak naprawdę nie można zrobić zmiennej ze stałej. Operator const_cast sprawia, że kompilator “przymyka oko”, pozwalając przesłać stałą do funkcji, która oczekuje zmiennej. Ta funkcja będzie mogła tylko czytać ten parametr! Gdy spróbuje zapisać – kompilator zaprotestuje.
5. (T)obj, T(obj)
Najsilniejsza konstrukcja. Nie używaj jej, bo:
- automatycznie dobiera taki typ rzutowania, na który pozwoli kompilator. Dlatego jest niebezpieczna – ostatecznie, mimo woli, możesz wylądować w reinterpret_cast, i rzutować typy całkiem niekompatybilne, bez słowa skargi od kompilatora. Próbuje dopasować rodzaj rzutowania w kolejności:
const_cast -> static(const)_cast -> reinterpret(const)_cast
Dobra rada
Kiedy musisz rzutować typy – stosuj rzutowanie najbardziej restrykcyjne. Wtedy kompilator Cię chroni przed popełnieniem błędu. Próbuj w kolejności:
const_cast -> static_cast -> dynamic_cast -> reinterpret_cast
I na koniec – nie używaj konstrukcji (T)obj ani T(obj), a nie wpadniesz w kłopoty ;)
April 16th, 2012 on 16:22
Co do reinterpret_cast – jest to chyba jedyny operator, który pozwala dostać sie do podklasy jakiejś większej klasy wielodziedziczącej.
Jeśli masz
class A : public B, public C {}
to do B albo C dostaniesz się tylko przez reinterpret_cast(ptr).
July 27th, 2012 on 19:46
Dlaczego dynamic_cast miałby sobie z tym nie poradzić?