Słyszałem, że ta static_cast
funkcja powinna być preferowana od rzutowania w stylu C lub prostego rzutowania w stylu funkcji. Czy to prawda? Dlaczego?
Słyszałem, że ta static_cast
funkcja powinna być preferowana od rzutowania w stylu C lub prostego rzutowania w stylu funkcji. Czy to prawda? Dlaczego?
Odpowiedzi:
Głównym powodem jest to, że klasyczne odlewy C nie dokonują rozróżnienia między tym, co nazywamy static_cast<>()
, reinterpret_cast<>()
, const_cast<>()
, i dynamic_cast<>()
. Te cztery rzeczy są zupełnie inne.
A static_cast<>()
jest zwykle bezpieczny. Istnieje poprawna konwersja w języku lub odpowiedni konstruktor, który to umożliwia. Jedynie raz jest to trochę ryzykowne, kiedy rzucisz się na dziedziczną klasę; musisz upewnić się, że obiekt jest w rzeczywistości potomkiem, o którym się twierdzi, że jest zewnętrzny w stosunku do języka (jak flaga w obiekcie). A dynamic_cast<>()
jest bezpieczne, dopóki wynik jest sprawdzany (wskaźnik) lub możliwy wyjątek jest uwzględniany (odniesienie).
Z drugiej strony A reinterpret_cast<>()
(lub a const_cast<>()
) jest zawsze niebezpieczne. Mówisz kompilatorowi: „zaufaj mi: wiem, że to nie wygląda foo
(wygląda na to, że nie można go modyfikować), ale tak jest”.
Pierwszym problemem jest to, że prawie niemożliwe jest określenie, który wystąpi w obsadzie w stylu C, bez patrzenia na duże i rozproszone fragmenty kodu i znajomości wszystkich reguł.
Załóżmy, że:
class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;
CMyBase *pSomething; // filled somewhere
Teraz te dwie są kompilowane w ten sam sposób:
CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked
pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
// Safe; as long as we checked
// but harder to read
Zobaczmy jednak prawie identyczny kod:
CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert
pOther = (CMyOtherStuff*)(pSomething); // No compiler error.
// Same as reinterpret_cast<>
// and it's wrong!!!
Jak widać, nie ma łatwego sposobu na rozróżnienie tych dwóch sytuacji bez wiedzy o wszystkich zaangażowanych klasach.
Drugi problem polega na tym, że rzutowanie w stylu C jest zbyt trudne do zlokalizowania. W złożonych wyrażeniach może być bardzo trudno zobaczyć rzutki w stylu C. Jest praktycznie niemożliwe napisanie zautomatyzowanego narzędzia, które musi zlokalizować rzutowania w stylu C (na przykład narzędzie wyszukiwania) bez pełnego interfejsu kompilatora C ++. Z drugiej strony łatwo jest wyszukać „static_cast <” lub „reinterpret_cast <”.
pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
// No compiler error.
// but the presence of a reinterpret_cast<> is
// like a Siren with Red Flashing Lights in your code.
// The mere typing of it should cause you to feel VERY uncomfortable.
Oznacza to, że nie tylko rzuty w stylu C są bardziej niebezpieczne, ale znacznie trudniej jest znaleźć je wszystkie, aby upewnić się, że są prawidłowe.
static_cast
do odrzucania hierarchii dziedziczenia, ale raczej dynamic_cast
. Zwróci to wskaźnik zerowy lub prawidłowy wskaźnik.
static_cast
w tej sytuacji. dynamic_cast
może być bezpieczniejszy, ale nie zawsze jest to najlepsza opcja. Czasami wiesz, że wskaźnik wskazuje na podtyp, nieprzezroczysty dla kompilatora, a a static_cast
jest szybszy. W co najmniej niektórych środowiskach dynamic_cast
wymaga opcjonalnej obsługi kompilatora i kosztu działania (włączenie RTTI) i możesz nie chcieć włączać go tylko dla kilku kontroli, które możesz wykonać samodzielnie. RTTI C ++ jest tylko jednym możliwym rozwiązaniem problemu.
static_cast
. Odpowiednikiem C reinterpret_cast
jest *(destination_type *)&
, tzn. Pobranie adresu obiektu, rzutowanie tego adresu na wskaźnik na inny typ, a następnie dereferencje. Z wyjątkiem przypadku typów znaków lub niektórych typów struktur, dla których C definiuje zachowanie tego konstruktu, generalnie powoduje to niezdefiniowane zachowanie w C.
int
(i int
tylko), dlaczego użycie static_cast<int>
vs. (int)
jako jedyna korzyść wydaje się być ze zmiennymi klasowymi i wskaźnikami. Poproś o rozwinięcie tego.
int
dynamic_cast
nie ma zastosowania, ale wszystkie pozostałe powody są ważne . Na przykład: powiedzmy, że v
jest parametrem funkcji zadeklarowanym jako float
, to (int)v
jest static_cast<int>(v)
. Ale jeśli zmienisz parametr na float*
, (int)v
po cichu staje się reinterpret_cast<int>(v)
chwilowo static_cast<int>(v)
nielegalne i poprawnie przechwytywane przez kompilator.
Jedna pragmatyczna wskazówka: możesz łatwo wyszukać słowo kluczowe static_cast w kodzie źródłowym, jeśli planujesz uporządkować projekt.
int
parametrem.
W skrócie :
static_cast<>()
daje ci możliwość sprawdzania czasu kompilacji, nie ma obsady w stylu C.static_cast<>()
może być łatwo zauważony w dowolnym miejscu w kodzie źródłowym C ++; przeciwnie, obsada C_Style jest trudniejsza do wykrycia.- Intencje są przekazywane znacznie lepiej przy użyciu rzutowań C ++.
Więcej wyjaśnień :
Obsada statyczna wykonuje konwersje między zgodnymi typami . Jest podobny do obsady w stylu C, ale jest bardziej restrykcyjny. Na przykład rzutowanie w stylu C umożliwia wskaźnikowi całkowitemu wskazywanie znaku.
char c = 10; // 1 byte int *p = (int*)&c; // 4 bytes
Ponieważ skutkuje to 4-bajtowym wskaźnikiem wskazującym na 1 bajt przydzielonej pamięci, zapis do tego wskaźnika spowoduje błąd w czasie wykonywania lub nadpisze przylegającą pamięć.
*p = 5; // run-time error: stack corruption
W przeciwieństwie do rzutowania w stylu C, rzutowanie statyczne pozwoli kompilatorowi sprawdzić, czy typy danych wskaźnika i pointee są kompatybilne, co pozwala programiście przechwycić to nieprawidłowe przypisanie wskaźnika podczas kompilacji.
int *q = static_cast<int*>(&c); // compile-time error
Przeczytaj więcej na temat:
Jaka jest różnica między rzutowaniem static_cast <> a rzutowaniem w stylu C
i
rzutowaniem regularnym vs. static_cast vs. dynamic_cast
static_cast<>()
jest to bardziej czytelne. Mam na myśli, że czasami tak jest, ale przez większość czasu - szczególnie na podstawowych typach liczb całkowitych - jest po prostu okropnie i niepotrzebnie gadatliwy. Na przykład: Jest to funkcja, która zamienia bajty 32-bitowego słowa. static_cast<uint##>()
Czytanie przy użyciu rzutów byłoby prawie niemożliwe , ale dość łatwe do zrozumienia przy użyciu (uint##)
rzutów. Zdjęcie kodu: imgur.com/NoHbGve
always
. (ale w większości przypadków tak) Są pewne przypadki, w których rzutowanie w stylu c jest znacznie bardziej czytelne. To jeden z powodów, dla których casting w stylu c jest wciąż aktywny i działa w imho c ++. :) Nawiasem mówiąc, był to bardzo ładny przykład
(uint32_t)(uint8_t)
), aby osiągnąć, że bajty oprócz najniższych są resetowane. Do tego jest bitowe i ( 0xFF &
). Użycie obsad zaciemnia intencję.
Pytanie jest większe niż tylko użycie rzutowania static_cast lub rzutowania w stylu C, ponieważ podczas korzystania z rzutów w stylu C zachodzą różne rzeczy. Operatory rzutowania w C ++ mają na celu uwydatnienie tych operacji.
Na powierzchni rzutowania w stylu static_cast i C wyglądają tak samo, na przykład podczas rzutowania jednej wartości na drugą:
int i;
double d = (double)i; //C-style cast
double d2 = static_cast<double>( i ); //C++ cast
Oba z nich podają wartość całkowitą na podwójną. Jednak podczas pracy ze wskaźnikami sprawy stają się bardziej skomplikowane. kilka przykładów:
class A {};
class B : public A {};
A* a = new B;
B* b = (B*)a; //(1) what is this supposed to do?
char* c = (char*)new int( 5 ); //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error
W tym przykładzie (1) być może OK, ponieważ obiekt wskazywany przez A jest tak naprawdę instancją B. Ale co, jeśli nie wiesz w tym momencie kodu, na co właściwie wskazuje? (2) może całkowicie legalny (chcesz spojrzeć tylko na jeden bajt liczby całkowitej), ale może to być również błąd, w którym to przypadku błąd byłby miły, jak (3). Operatory rzutowania w C ++ mają na celu ujawnienie tych problemów w kodzie, zapewniając błędy kompilacji lub wykonania w miarę możliwości.
Zatem do ścisłego „rzutowania wartości” możesz użyć static_cast. Jeśli chcesz odlewania polimorficznego wskaźników w czasie wykonywania, użyj dynamic_cast. Jeśli naprawdę chcesz zapomnieć o typach, możesz użyć reintrepret_cast. Aby po prostu wyrzucić const przez okno, jest const_cast.
Po prostu uwydatniają kod, dzięki czemu wygląda na to, że wiesz, co robiłeś.
static_cast
oznacza, że nie możesz przypadkowo const_cast
lub reinterpret_cast
, co jest dobrą rzeczą.
Zobacz Efektywne C ++ Wprowadzenie
Chodzi o to, ile bezpieczeństwa typu chcesz narzucić.
Kiedy piszesz (bar) foo
(co jest równoważne zreinterpret_cast<bar> foo
jeśli nie podałeś operatora konwersji typu), mówisz kompilatorowi, aby zignorował bezpieczeństwo typu, i po prostu rób to, co mu kazano.
Kiedy piszesz static_cast<bar> foo
, pytasz kompilatora, aby przynajmniej sprawdził, czy konwersja typu ma sens, a dla typów integralnych wstawił kod konwersji.
EDYCJA 26.02.2014
Napisałem tę odpowiedź ponad 5 lat temu i pomyliłem się. (Patrz komentarze.) Ale wciąż zyskuje poparcie!
static_cast<bar>(foo)
w nawiasach. To samo dotyczy reinterpret_cast<bar>(foo)
.
Rzutów w stylu C można łatwo przeoczyć w bloku kodu. Rzutowania w stylu C ++ są nie tylko lepszą praktyką; oferują znacznie większą elastyczność.
reinterpret_cast pozwala na konwersję typu całkowego na wskaźnikowy, jednak może być niebezpieczny, jeśli zostanie niewłaściwie użyty.
static_cast oferuje dobrą konwersję typów liczbowych, np. z wyliczeń na ints lub ints na zmiennoprzecinkowe lub dowolne typy danych, których jesteś pewny. Nie wykonuje żadnych kontroli w czasie wykonywania.
Natomiast dynamic_cast wykona te kontrole, oznaczając wszelkie niejednoznaczne przypisania lub konwersje. Działa tylko w przypadku wskaźników i referencji i powoduje obciążenie.
Jest kilka innych, ale są to główne, na które się natkniesz.
static_cast, oprócz manipulowania wskaźnikami do klas, może być również używany do wykonywania konwersji wyraźnie zdefiniowanych w klasach, a także do wykonywania standardowych konwersji między podstawowymi typami:
double d = 3.14159265;
int i = static_cast<int>(d);
static_cast<int>(d)
, kiedy (int)d
jest o wiele bardziej zwięzły i czytelny? (Mam na myśli w przypadku typów podstawowych, a nie wskaźników obiektowych.)
(int)d
kiedy int{d}
jest o wiele bardziej czytelny? Konstruktor, lub podobny do funkcji, jeśli masz ()
, składnia nie jest tak szybka, aby przekształcić się w koszmarny labirynt nawiasów w złożonych wyrażeniach. W takim przypadku byłoby to int i{d}
zamiast int i = (int)d
. O wiele lepiej IMO. To powiedziawszy, kiedy potrzebuję tymczasowego wyrażenia, używam static_cast
i nigdy nie używałem rzutów konstruktorów, nie sądzę. Używam tylko, (C)casts
gdy pośpiesznie piszę debugowanie cout
s ...