Aby zapoznać się z niektórymi przydatnymi konkretnymi przykładami użycia różnych rodzajów rzutów, możesz sprawdzić pierwszą odpowiedź na podobne pytanie w tym innym temacie .
TeaMonkie
2
Możesz znaleźć naprawdę dobre odpowiedzi na swoje pytanie powyżej. Chciałbym jednak dodać jeszcze jeden punkt, @ e.James „Nie ma nic, co mogliby zrobić nowi operatorzy rzutowania c ++, a rzutowanie w stylu c nie. Są one dodawane mniej więcej dla lepszej czytelności kodu”.
BreakBadSP
@BreakBadSP Nowe castingi służą nie tylko lepszej czytelności kodu. Są po to, aby utrudniać robienie niebezpiecznych rzeczy, takich jak odrzucanie const lub rzucanie wskaźników zamiast ich wartości. static_cast ma znacznie mniej możliwości zrobienia czegoś niebezpiecznego niż obsada w stylu ac!
FourtyTwo
@FourtyTwo zgodził się
BreakBadSP
Odpowiedzi:
2569
static_castto pierwsza obsada, której powinieneś spróbować użyć. Robi takie rzeczy, jak niejawna konwersja między typami (np. intDo floatlub wskaźnik do void*), a także może wywoływać jawne funkcje konwersji (lub niejawne). W wielu przypadkach wyraźne stwierdzenie static_castnie jest konieczne, ale należy zauważyć, że T(something)składnia jest równoważna (T)somethingi należy jej unikać (więcej na ten temat później). A T(something, something_else)jest jednak bezpieczne i gwarantuje wywołanie konstruktora.
static_castmoże również rzutować poprzez hierarchie dziedziczenia. Nie jest konieczne podczas rzucania w górę (w kierunku klasy podstawowej), ale podczas rzucania w dół można go używać, dopóki nie przejdzie w wyniku virtualdziedziczenia. Nie sprawdza jednak i jest niezdefiniowanym zachowaniem static_castsprowadzającym hierarchię do typu, który w rzeczywistości nie jest typem obiektu.
const_castmoże być użyty do usunięcia lub dodania constdo zmiennej; żadna inna obsada C ++ nie jest w stanie go usunąć (nawet reinterpret_cast). Należy zauważyć, że modyfikowanie poprzedniej constwartości jest niezdefiniowane tylko wtedy, gdy zmienna oryginalna to const; jeśli użyjesz go do constzdjęcia odniesienia do czegoś, co nie zostało zadeklarowane const, jest to bezpieczne. Może to być przydatne na przykład przy przeciążaniu funkcji składowych na podstawie const. Można go również użyć do dodania constdo obiektu, na przykład do wywołania przeciążenia funkcji elementu.
const_castdziała również podobnie volatile, choć jest to mniej powszechne.
dynamic_castsłuży wyłącznie do radzenia sobie z polimorfizmem. Możesz rzutować wskaźnik lub odwołanie do dowolnego typu polimorficznego na dowolny inny typ klasy (typ polimorficzny ma co najmniej jedną funkcję wirtualną, zadeklarowaną lub odziedziczoną). Możesz go używać do czegoś więcej niż tylko rzucania w dół - możesz rzucać na boki, a nawet na inny łańcuch. dynamic_castBędzie odszukać żądany obiekt i zwraca go, jeśli to możliwe. Jeśli nie może, wróci nullptrw przypadku wskaźnika lub wrzuci std::bad_castw przypadku odniesienia.
dynamic_castma jednak pewne ograniczenia. Nie działa, jeśli w hierarchii dziedziczenia znajduje się wiele obiektów tego samego typu (tak zwany „przerażający diament”) i nie korzystasz z virtualdziedziczenia. Może również przechodzić tylko przez dziedziczenie publiczne - zawsze nie uda mu się przejść protectedani privateodziedziczyć. Rzadko jest to jednak problem, ponieważ takie formy dziedziczenia są rzadkie.
reinterpret_castjest najbardziej niebezpieczną obsadą i powinna być używana bardzo oszczędnie. Przekształca jeden typ bezpośrednio w inny - na przykład rzutuje wartość z jednego wskaźnika na inny, lub przechowuje wskaźnik w jednym intlub wielu innych paskudnych rzeczach. W dużej mierze jedyną gwarancją, którą otrzymujesz, reinterpret_castjest to, że normalnie, jeśli rzutujesz wynik z powrotem na oryginalny typ, otrzymasz dokładnie tę samą wartość (ale nie, jeśli typ pośredni jest mniejszy niż typ oryginalny). Istnieje również szereg konwersji, reinterpret_castktórych nie można wykonać. Jest używany przede wszystkim do szczególnie dziwnych konwersji i manipulacji bitami, takich jak przekształcanie surowego strumienia danych w rzeczywiste dane lub przechowywanie danych w małych bitach wskaźnika w wyrównanych danych.
C-style cast i funkcja stylu odlewane są odlewy z użyciem (type)objectlub type(object), odpowiednio, i są funkcjonalnie równoważne. Są one zdefiniowane jako pierwsza z następujących czynności:
const_cast
static_cast (ignorując ograniczenia dostępu)
static_cast (patrz wyżej) const_cast
reinterpret_cast
reinterpret_cast, następnie const_cast
Dlatego może być używany jako zamiennik innych rzutów w niektórych przypadkach, ale może być wyjątkowo niebezpieczny ze względu na możliwość przekształcenia się w rzut reinterpret_cast, a ten ostatni powinien być preferowany, gdy potrzebne jest jawne rzucanie, chyba że masz pewność, static_castże powiedzie się lub reinterpret_castzakończy się niepowodzeniem . Nawet wtedy rozważ dłuższą, bardziej wyraźną opcję.
Rzutowania w stylu C również ignorują kontrolę dostępu podczas wykonywania static_castrzutu, co oznacza, że mogą wykonać operację, jakiej nie może wykonać żaden inny rzut. Jest to jednak głównie kludge, a moim zdaniem jest to kolejny powód do unikania rzutów w stylu C.
Dynamic_cast jest tylko dla typów polimorficznych. musisz go używać tylko podczas rzucania do klasy pochodnej. static_cast jest z pewnością pierwszą opcją, chyba że potrzebujesz dynamicznej funkcji dynamic_cast. Ogólnie rzecz biorąc, nie jest to jakaś cudowna srebrna kula „sprawdzająca typ”.
czerwiec
2
Świetna odpowiedź! Jedna szybka uwaga: static_cast może być konieczne, aby rzucić hierarchię w przypadku, gdy masz pochodną * i rzucić w Base * &, ponieważ podwójne wskaźniki / referencje nie automagicznie rzutują hierarchii. Taką (szczerze mówiąc, nieczęstą) sytuację spotkałem dwie minuty temu. ;-)
bartgol
5
* „żadna inna obsada C ++ nie jest w stanie usunąć const(nawet reinterpret_cast)”… naprawdę? Co reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686,
29
Myślę, że ważnym brakującym szczegółem powyżej jest to, że dynamic_cast ma ujemny wpływ na wydajność w czasie wykonywania w porównaniu do static lub reinterpret_cast. Jest to ważne, np. W oprogramowaniu czasu rzeczywistego.
jfritz42
5
Warto wspomnieć, że reinterpret_castczęsto jest to broń z wyboru w przypadku zestawów nieprzezroczystych typów danych API
Class Skeleton
333
Służy dynamic_castdo konwertowania wskaźników / referencji w hierarchii dziedziczenia.
Użyj static_castdo konwersji zwykłego typu.
Służy reinterpret_castdo reinterpretacji wzorów bitowych na niskim poziomie. Używaj z dużą ostrożnością.
Użyj const_castdo rzucania const/volatile. Unikaj tego, chyba że utkniesz przy użyciu niepoprawnego interfejsu API.
Uważaj z dynamic_cast. Opiera się na RTTI i nie będzie działać zgodnie z oczekiwaniami między granicami bibliotek współdzielonych. Po prostu dlatego, że budujesz bibliotekę wykonywalną i współdzieloną niezależnie, nie ma znormalizowanego sposobu synchronizacji RTTI między różnymi kompilacjami. Z tego powodu w bibliotece Qt istnieje qobject_cast <>, który używa informacji o typie QObject do sprawdzania typów.
user3150128,
198
(Wiele wyjaśnień teoretycznych i koncepcyjnych podano powyżej)
Poniżej kilka praktycznych przykładów, gdy użyłem static_cast , dynamic_cast , const_cast , reinterpret_cast .
OnEventData(void* pData){......// pData is a void* pData, // EventData is a structure e.g. // typedef struct _EventData {// std::string id;// std:: string remote_id;// } EventData;// On Some Situation a void pointer *pData// has been static_casted as // EventData* pointer EventData*evtdata =static_cast<EventData*>(pData);.....}
Teoria niektórych innych odpowiedzi jest dobra, ale wciąż myląca, widząc te przykłady po przeczytaniu innych odpowiedzi naprawdę sprawia, że wszystkie mają sens. To bez przykładów, wciąż nie byłem pewien, ale dzięki nim jestem teraz pewien, co oznaczają inne odpowiedzi.
Solx
1
O ostatnim użyciu reinterpret_cast: czy to nie to samo, co używanie static_cast<char*>(&val)?
Lorenzo Belli
3
@LorenzoBelli Oczywiście, że nie. Próbowałeś? Ten ostatni nie jest poprawnym C ++ i blokuje kompilację. static_castdziała tylko między typami z określonymi konwersjami, widoczną relacją według dziedziczenia lub do / z void *. Na wszystko inne są inne obsady. reinterpret castna dowolny char *typ zezwala się na odczyt reprezentacji dowolnego obiektu - i jest to jeden z niewielu przypadków, w których to słowo kluczowe jest przydatne, a nie szalony generator implementacji / niezdefiniowane zachowanie. Ale nie jest to uważane za „normalną” konwersję, więc nie jest dozwolone (zwykle) bardzo konserwatywne static_cast.
underscore_d
2
reinterpret_cast jest dość powszechny, gdy pracujesz z oprogramowaniem systemowym, takim jak bazy danych. W większości przypadków piszesz własny menedżer stron, który nie ma pojęcia o typie danych przechowywanych na stronie i zwraca po prostu pusty wskaźnik. Do wyższych poziomów należy dokonać reinterpretacji obsady i wywnioskowanie tego, jak chcą.
Sohaib
1
Przykład const_cast wykazuje niezdefiniowane zachowanie. Zmiennej zadeklarowanej jako const nie można de-const-ed. Jednak zmienna zadeklarowana jako non-const, która jest przekazywana do funkcji przyjmującej odwołanie do const, może w tej funkcji zostać de-const -red bez jej UB.
Johann Gerell
99
Może to pomóc, jeśli znasz trochę elementów wewnętrznych ...
static_cast
Kompilator C ++ już wie, jak konwertować typy skalerów, takie jak float na int. Użyj static_castdla nich.
Gdy poprosisz kompilator o konwersję z typu Ana B, konstruktor static_castwywołań Bprzechodzi Ajako param. Alternatywnie Amoże mieć operator konwersji (tj A::operator B().). Jeśli Bnie ma takiego konstruktora lub Anie ma operatora konwersji, pojawia się błąd czasu kompilacji.
Przesyłanie z A*do B*zawsze kończy się powodzeniem, jeśli A i B są w hierarchii dziedziczenia (lub nieważne), w przeciwnym razie wystąpi błąd kompilacji.
Gotcha : Jeśli rzutujesz wskaźnik bazowy na wskaźnik pochodny, ale jeśli rzeczywisty obiekt nie jest tak naprawdę pochodnym typem, nie pojawi się błąd. Otrzymujesz zły wskaźnik i najprawdopodobniej segfault w czasie wykonywania. To samo dotyczy A&się B&.
Gotcha : Cast z Derived do Base lub viceversa tworzy nową kopię! Dla osób pochodzących z C # / Java może to być ogromna niespodzianka, ponieważ wynik jest w zasadzie odciętym obiektem utworzonym z Derived.
dynamic_cast
dynamic_cast używa informacji o typie środowiska wykonawczego, aby ustalić, czy rzutowanie jest prawidłowe. Na przykład, (Base*)aby (Derived*)może zakończyć się niepowodzeniem, jeśli wskaźnik nie jest w rzeczywistości typu pochodnej.
Oznacza to, że dynamic_cast jest bardzo drogi w porównaniu do static_cast!
Na A*celu B*, jeśli obsada jest nieprawidłowy następnie dynamic_cast powróci nullptr.
Dla A&aby B&jeśli obsada jest nieprawidłowy następnie dynamic_cast rzuci bad_cast wyjątek.
W przeciwieństwie do innych rzutów, istnieje narzut związany z czasem działania.
const_cast
Podczas gdy static_cast może robić non-const, ale nie może być odwrotnie. Const_cast może działać na dwa sposoby.
Jednym z przykładów, w których się to przydaje, jest iteracja przez jakiś kontener, set<T>który zwraca tylko jego elementy jako const, aby upewnić się, że nie zmienisz jego klucza. Jeśli jednak masz zamiar zmodyfikować niekluczowe elementy obiektu, powinno być w porządku. Możesz użyć const_cast, aby usunąć constness.
Innym przykładem jest, gdy chcesz wdrożyć, T& SomeClass::foo()a także const T& SomeClass::foo() const. Aby uniknąć powielania kodu, możesz zastosować const_cast, aby zwrócić wartość jednej funkcji z drugiej.
reinterpret_cast
To w zasadzie mówi, że bierz te bajty w tym miejscu pamięci i traktuj to jako dany obiekt.
Na przykład możesz załadować 4 bajty liczby zmiennoprzecinkowej do 4 bajtów liczby całkowitej, aby zobaczyć, jak wyglądają bity w liczbach zmiennoprzecinkowych.
Oczywiście, jeśli dane nie są poprawne dla tego typu, możesz dostać segfault.
Dla tej obsady nie ma narzutów związanych z czasem wykonywania.
Dodałem informacje o operatorze konwersji, ale jest też kilka innych rzeczy, które należy naprawić i nie czuję się zbyt komfortowo aktualizując to za bardzo. Przedmioty to: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Dostajesz UB, co może skutkować awarią w czasie wykonywania, jeśli masz szczęście. 2. Odlewy dynamiczne można również stosować w odlewach krzyżowych. 3. Rzutowanie Const może w niektórych przypadkach powodować UB. Użycie mutablemoże być lepszym wyborem do wdrożenia logicznej stałości.
Adrian
1
@Adrian masz rację pod każdym względem. Odpowiedź jest napisana dla ludzi mniej więcej na poziomie początkującym i nie chciałem ich przytłaczać wszystkimi innymi komplikacjami, które przychodzą mutable, castingiem krzyżowym itp.
Nigdy nie korzystałem reinterpret_casti zastanawiam się, czy trafienie na skrzynkę, która tego potrzebuje, nie jest zapachem złego designu. W bazie kodu, nad którą pracuję, dynamic_castużywa się dużo. Różnica static_castpolega na tym, że dynamic_castsprawdza środowisko uruchomieniowe , które może (bezpieczniej) lub nie (więcej narzutu) być tym, czego chcesz (patrz msdn ).
Użyłem reintrepret_cast w jednym celu - wydobycie bitów z podwójnego (taki sam rozmiar jak długi na mojej platformie).
Joshua
2
reinterpret_cast jest potrzebny np. do pracy z obiektami COM. CoCreateInstance () ma parametr wyjściowy typu void ** (ostatni parametr), w którym przekażesz wskaźnik zadeklarowany jako np. „INetFwPolicy2 * pNetFwPolicy2”. Aby to zrobić, musisz napisać coś takiego jak reinterpret_cast <void **> (& pNetFwPolicy2).
Serge Rogatch
1
Być może istnieje inne podejście, ale używam reinterpret_castdo wydobywania fragmentów danych z tablicy. Na przykład, jeśli mam char*duży bufor pełen spakowanych danych binarnych, które muszę przejść i uzyskać pojedyncze prymitywy różnych typów. Coś w tym stylu:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
James Matta
Nigdy nie korzystałem reinterpret_cast, nie ma zbyt wielu zastosowań.
Pika the Wizard of the Whales
Osobiście widziałem reinterpret_castużywane tylko z jednego powodu. Widziałem dane surowego obiektu przechowywane w typie danych „obiektu blob” w bazie danych, a następnie, gdy dane są pobierane z bazy danych, reinterpret_castsą używane do przekształcenia tych surowych danych w obiekt.
ImaginaryHuman072889
15
Oprócz innych dotychczasowych odpowiedzi, tutaj jest nieoczywisty przykład, w którym static_castnie jest wystarczający, więc reinterpret_castjest potrzebny. Załóżmy, że istnieje funkcja, która w parametrze wyjściowym zwraca wskaźniki do obiektów różnych klas (które nie dzielą wspólnej klasy podstawowej). Prawdziwym przykładem takiej funkcji jest CoCreateInstance()(zobacz ostatni parametr, który w rzeczywistości jest void**). Załóżmy, że żądasz określonej klasy obiektu od tej funkcji, więc z góry znasz typ wskaźnika (co często robisz dla obiektów COM). W tym przypadku nie można rzutować wskaźnik do wskaźnika do swojej void**ze static_castmusisz reinterpret_cast<void**>(&yourPointer).
W kodzie:
#include<windows.h>#include<netfw.h>.....INetFwPolicy2* pNetFwPolicy2 =nullptr;
HRESULT hr =CoCreateInstance(__uuidof(NetFwPolicy2),nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),//static_cast<void**>(&pNetFwPolicy2) would give a compile errorreinterpret_cast<void**>(&pNetFwPolicy2));
Jednak static_castdziała na prostych wskaźników (nie wskaźniki do wskaźników), więc powyższy kod może zostać przepisany w celu uniknięcia reinterpret_cast(w cenie dodatkową zmienną) w następujący sposób:
Czy nie działa coś takiego &static_cast<void*>(pNetFwPolicy2)zamiast static_cast<void**>(&pNetFwPolicy2)?
jp48,
9
Podczas gdy inne odpowiedzi ładnie opisywały wszystkie różnice między rzutami w C ++, chciałbym dodać krótką notatkę, dlaczego nie powinieneś używać rzutów w stylu C (Type) vari Type(var).
Dla początkujących w C ++ rzutowania w stylu C wyglądają jak nadzbiór nad rzutowaniami C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) i ktoś może je preferować nad rzutami C ++ . W rzeczywistości obsada w stylu C jest nadzbiorem i jest krótsza do napisania.
Głównym problemem w obsadach w stylu C jest to, że ukrywają prawdziwą intencję twórców obsady. Rzutowania w stylu C mogą wykonywać praktycznie wszystkie typy rzutowania, od normalnie bezpiecznych rzutów wykonywanych przez static_cast <> () i dynamic_cast <> () do potencjalnie niebezpiecznych rzutowań, takich jak const_cast <> (), gdzie można zmienić modyfikator const, aby zmienne const można modyfikować i reinterpretować <cast (>), które mogą nawet ponownie interpretować wartości całkowite na wskaźniki.
Oto próbka.
int a=rand();// Random number.int* pa1=reinterpret_cast<int*>(a);// OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.int* pa2=static_cast<int*>(a);// Compiler error.int* pa3=dynamic_cast<int*>(a);// Compiler error.int* pa4=(int*) a;// OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.*pa4=5;// Program crashes.
Głównym powodem, dla którego dodano obsady C ++ do tego języka, było umożliwienie programistom wyjaśnienia swoich zamiarów - dlaczego zamierza to zrobić. Używając rzutowań w stylu C, które są w pełni poprawne w C ++, czynisz swój kod mniej czytelnym i bardziej podatnym na błędy, szczególnie dla innych programistów, którzy nie stworzyli twojego kodu. Aby twój kod był bardziej czytelny i wyraźny, zawsze powinieneś preferować rzutowania w C ++ niż rzutowania w stylu C.
Oto krótki cytat z książki Bjarne Stroustrup (autor C ++) The C ++ Programming Language 4. wydanie - strona 302.
Ta rzutowanie w stylu C jest znacznie bardziej niebezpieczne niż nazwane operatory konwersji, ponieważ notacja jest trudniejsza do zauważenia w dużym programie, a rodzaj konwersji zamierzony przez programistę nie jest jednoznaczny.
Tylko linia (4) kompiluje się bez błędów. Tylko reinterpret_cast można użyć do konwersji wskaźnika na obiekt na wskaźnik na dowolny niepowiązany typ obiektu.
Należy zauważyć, że: dynamic_cast nie powiedzie się w czasie wykonywania, jednak w większości kompilatorów również nie uda się go skompilować, ponieważ w strukturze rzutowanego wskaźnika nie ma żadnych funkcji wirtualnych, co oznacza, że dynamic_cast będzie działał tylko ze wskaźnikami klasy polimorficznej .
Kiedy używać obsady C ++ :
Użyj static_cast jako odpowiednika rzutowania w stylu C, który dokonuje konwersji wartości, lub gdy musimy jawnie podwyższyć wskaźnik z klasy do jej nadklasy.
Użyj const_cast, aby usunąć kwalifikator const.
Użyj reinterpret_cast, aby wykonać niebezpieczne konwersje typów wskaźników na liczby całkowite i inne oraz na inne typy wskaźników. Używaj tego tylko wtedy, gdy wiemy, co robimy i rozumiemy problemy z aliasingiem.
static_castwidok kontra dynamic_castkontra reinterpret_castwewnętrzne na downcast / upcast
W tej odpowiedzi chcę porównać te trzy mechanizmy na konkretnym przykładzie upcast / downcast i przeanalizować, co dzieje się z podstawowymi wskaźnikami / pamięcią / zestawem, aby uzyskać konkretne zrozumienie ich porównania.
Wierzę, że da to dobrą intuicję na temat różnic między tymi obsadami:
static_cast: robi jedno przesunięcie adresu w czasie wykonywania (mały wpływ w czasie działania) i nie sprawdza bezpieczeństwa, czy downcast jest poprawny.
dyanamic_cast: robi to samo przesunięcie adresu jak w czasie wykonywania static_cast, ale także i kosztowną kontrolę bezpieczeństwa, czy downcast jest poprawny przy użyciu RTTI.
Ta kontrola bezpieczeństwa pozwala zapytać, czy wskaźnik klasy bazowej jest określonego typu w czasie wykonywania, sprawdzając, czy zwrot nullptrwskazuje nieprawidłowy downcast.
Dlatego jeśli twój kod nie jest w stanie tego sprawdzić nullptri podjąć prawidłowej akcji nie przerywania, powinieneś użyć static_castzamiast dynamicznego rzutowania.
Jeśli przerwanie jest jedyną czynnością, jaką może wykonać Twój kod, być może chcesz tylko włączyć dynamic_castkompilacje debugowania ( -NDEBUG) i użyć static_castinaczej, np. Tak jak tutaj , aby nie spowalniać szybkiego uruchamiania.
reinterpret_cast: nic nie robi w czasie wykonywania, nawet przesunięcie adresu. Wskaźnik musi wskazywać dokładnie odpowiedni typ, nawet klasa podstawowa nie działa. Zasadniczo nie chcesz tego, chyba że zaangażowane są strumienie surowych bajtów.
Rozważ następujący przykład kodu:
main.cpp
#include<iostream>struct B1 {
B1(int int_in_b1): int_in_b1(int_in_b1){}virtual~B1(){}void f0(){}virtualint f1(){return1;}int int_in_b1;};struct B2 {
B2(int int_in_b2): int_in_b2(int_in_b2){}virtual~B2(){}virtualint f2(){return2;}int int_in_b2;};struct D :public B1,public B2 {
D(int int_in_b1,int int_in_b2,int int_in_d): B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d){}void d(){}int f2(){return3;}int int_in_d;};int main(){
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1,2,3};// The memory layout must support the virtual method call use case.
b2s[0]=&b2;// An upcast is an implicit static_cast<>().
b2s[1]=&d;
std::cout <<"&d "<<&d << std::endl;
std::cout <<"b2s[0] "<< b2s[0]<< std::endl;
std::cout <<"b2s[1] "<< b2s[1]<< std::endl;
std::cout <<"b2s[0]->f2() "<< b2s[0]->f2()<< std::endl;
std::cout <<"b2s[1]->f2() "<< b2s[1]->f2()<< std::endl;// Now for some downcasts.// Cannot be done implicitly// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]// dp = (b2s[0]);// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp =static_cast<D*>(b2s[0]);
std::cout <<"static_cast<D*>(b2s[0]) "<< dp << std::endl;
std::cout <<"static_cast<D*>(b2s[0])->int_in_d "<< dp->int_in_d << std::endl;// OK
dp =static_cast<D*>(b2s[1]);
std::cout <<"static_cast<D*>(b2s[1]) "<< dp << std::endl;
std::cout <<"static_cast<D*>(b2s[1])->int_in_d "<< dp->int_in_d << std::endl;// Segfault because dp is nullptr.
dp =dynamic_cast<D*>(b2s[0]);
std::cout <<"dynamic_cast<D*>(b2s[0]) "<< dp << std::endl;//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;// OK
dp =dynamic_cast<D*>(b2s[1]);
std::cout <<"dynamic_cast<D*>(b2s[1]) "<< dp << std::endl;
std::cout <<"dynamic_cast<D*>(b2s[1])->int_in_d "<< dp->int_in_d << std::endl;// Undefined behaviour to an unrelated memory address because this// did not calculate the offset to get from B2* to D*.
dp =reinterpret_cast<D*>(b2s[1]);
std::cout <<"reinterpret_cast<D*>(b2s[1]) "<< dp << std::endl;
std::cout <<"reinterpret_cast<D*>(b2s[1])->int_in_d "<< dp->int_in_d << std::endl;}
B1:+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:+0: pointer to virtual method table of D (for B1)+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)+12: value of int_in_b2
+16: value of int_in_d
Kluczowym faktem jest to, że struktura danych pamięci Dzawiera w sobie strukturę pamięci zgodną ze strukturą wewnętrzną B1i B2wewnętrzną.
Dlatego dochodzimy do krytycznego wniosku:
Upcast lub downcast musi jedynie przesunąć wartość wskaźnika o wartość znaną w czasie kompilacji
W ten sposób, gdy Dzostanie przekazany do tablicy typów bazowych, rzutowany typ faktycznie oblicza to przesunięcie i wskazuje coś, co wygląda dokładnie tak, jak prawidłowe B2w pamięci:
b2s[1]=&d;
poza tym, że ten ma Dzamiast tego vtable B2, dlatego wszystkie wirtualne połączenia działają transparentnie.
Teraz możemy wreszcie wrócić do odlewania czcionek i analizy naszego konkretnego przykładu.
Z wyjścia standardowego widzimy:
&d 0x7fffffffc930
b2s[1]0x7fffffffc940
Dlatego domyślnie static_castwykonane tam poprawnie obliczyło przesunięcie od pełnej Dstruktury danych w 0x7fffffffc930 do B2podobnej, która jest w 0x7fffffffc940. Wnioskujemy również, że między 0x7fffffffc930 a 0x7fffffffc940 prawdopodobnie są to B1dane i vtable.
Następnie w sekcjach spuszczanych łatwo jest zrozumieć, dlaczego zawiodły nieprawidłowe i dlaczego:
static_cast<D*>(b2s[0]) 0x7fffffffc910: kompilator właśnie poszedł w górę 0x10 w bajtach czasu kompilacji, aby spróbować przejść od a B2do zawierającegoD
Ale ponieważ b2s[0]nie było D, wskazuje teraz na nieokreślony region pamięci.
Najpierw jest sprawdzanie NULL i zwraca NULL, jeśli einput ma wartość NULL.
W przeciwnym razie ustawia niektóre argumenty w RDX, RSI i RDI oraz wywołania __dynamic_cast.
Nie mam teraz cierpliwości, aby dalej to analizować, ale jak powiedzieli inni, jedynym sposobem na to jest __dynamic_castdostęp do niektórych dodatkowych struktur danych w pamięci RTTI, które reprezentują hierarchię klas.
Dlatego musi zaczynać od B2wpisu dla tej tabeli, a następnie przejść tę hierarchię klas, aż stwierdzi, że vtable dla Dtypecast z b2s[0].
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940ten po prostu wierzy nam na ślepo: powiedzieliśmy, że jest Dadres b2s[1], a kompilator nie wykonuje obliczeń przesunięcia.
Ale to źle, ponieważ D faktycznie ma 0x7fffffffc930, a co 0x7fffffffc940 to struktura podobna do B2 wewnątrz D! Więc dostęp do śmieci.
Możemy to potwierdzić z przerażającego -O0zestawu, który po prostu przesuwa wartość:
Odpowiedzi:
static_cast
to pierwsza obsada, której powinieneś spróbować użyć. Robi takie rzeczy, jak niejawna konwersja między typami (np.int
Dofloat
lub wskaźnik dovoid*
), a także może wywoływać jawne funkcje konwersji (lub niejawne). W wielu przypadkach wyraźne stwierdzeniestatic_cast
nie jest konieczne, ale należy zauważyć, żeT(something)
składnia jest równoważna(T)something
i należy jej unikać (więcej na ten temat później). AT(something, something_else)
jest jednak bezpieczne i gwarantuje wywołanie konstruktora.static_cast
może również rzutować poprzez hierarchie dziedziczenia. Nie jest konieczne podczas rzucania w górę (w kierunku klasy podstawowej), ale podczas rzucania w dół można go używać, dopóki nie przejdzie w wynikuvirtual
dziedziczenia. Nie sprawdza jednak i jest niezdefiniowanym zachowaniemstatic_cast
sprowadzającym hierarchię do typu, który w rzeczywistości nie jest typem obiektu.const_cast
może być użyty do usunięcia lub dodaniaconst
do zmiennej; żadna inna obsada C ++ nie jest w stanie go usunąć (nawetreinterpret_cast
). Należy zauważyć, że modyfikowanie poprzedniejconst
wartości jest niezdefiniowane tylko wtedy, gdy zmienna oryginalna toconst
; jeśli użyjesz go doconst
zdjęcia odniesienia do czegoś, co nie zostało zadeklarowaneconst
, jest to bezpieczne. Może to być przydatne na przykład przy przeciążaniu funkcji składowych na podstawieconst
. Można go również użyć do dodaniaconst
do obiektu, na przykład do wywołania przeciążenia funkcji elementu.const_cast
działa również podobnievolatile
, choć jest to mniej powszechne.dynamic_cast
służy wyłącznie do radzenia sobie z polimorfizmem. Możesz rzutować wskaźnik lub odwołanie do dowolnego typu polimorficznego na dowolny inny typ klasy (typ polimorficzny ma co najmniej jedną funkcję wirtualną, zadeklarowaną lub odziedziczoną). Możesz go używać do czegoś więcej niż tylko rzucania w dół - możesz rzucać na boki, a nawet na inny łańcuch.dynamic_cast
Będzie odszukać żądany obiekt i zwraca go, jeśli to możliwe. Jeśli nie może, wrócinullptr
w przypadku wskaźnika lub wrzucistd::bad_cast
w przypadku odniesienia.dynamic_cast
ma jednak pewne ograniczenia. Nie działa, jeśli w hierarchii dziedziczenia znajduje się wiele obiektów tego samego typu (tak zwany „przerażający diament”) i nie korzystasz zvirtual
dziedziczenia. Może również przechodzić tylko przez dziedziczenie publiczne - zawsze nie uda mu się przejśćprotected
aniprivate
odziedziczyć. Rzadko jest to jednak problem, ponieważ takie formy dziedziczenia są rzadkie.reinterpret_cast
jest najbardziej niebezpieczną obsadą i powinna być używana bardzo oszczędnie. Przekształca jeden typ bezpośrednio w inny - na przykład rzutuje wartość z jednego wskaźnika na inny, lub przechowuje wskaźnik w jednymint
lub wielu innych paskudnych rzeczach. W dużej mierze jedyną gwarancją, którą otrzymujesz,reinterpret_cast
jest to, że normalnie, jeśli rzutujesz wynik z powrotem na oryginalny typ, otrzymasz dokładnie tę samą wartość (ale nie, jeśli typ pośredni jest mniejszy niż typ oryginalny). Istnieje również szereg konwersji,reinterpret_cast
których nie można wykonać. Jest używany przede wszystkim do szczególnie dziwnych konwersji i manipulacji bitami, takich jak przekształcanie surowego strumienia danych w rzeczywiste dane lub przechowywanie danych w małych bitach wskaźnika w wyrównanych danych.C-style cast i funkcja stylu odlewane są odlewy z użyciem
(type)object
lubtype(object)
, odpowiednio, i są funkcjonalnie równoważne. Są one zdefiniowane jako pierwsza z następujących czynności:const_cast
static_cast
(ignorując ograniczenia dostępu)static_cast
(patrz wyżej)const_cast
reinterpret_cast
reinterpret_cast
, następnieconst_cast
Dlatego może być używany jako zamiennik innych rzutów w niektórych przypadkach, ale może być wyjątkowo niebezpieczny ze względu na możliwość przekształcenia się w rzut
reinterpret_cast
, a ten ostatni powinien być preferowany, gdy potrzebne jest jawne rzucanie, chyba że masz pewność,static_cast
że powiedzie się lubreinterpret_cast
zakończy się niepowodzeniem . Nawet wtedy rozważ dłuższą, bardziej wyraźną opcję.Rzutowania w stylu C również ignorują kontrolę dostępu podczas wykonywania
static_cast
rzutu, co oznacza, że mogą wykonać operację, jakiej nie może wykonać żaden inny rzut. Jest to jednak głównie kludge, a moim zdaniem jest to kolejny powód do unikania rzutów w stylu C.źródło
const
(nawetreinterpret_cast
)”… naprawdę? Coreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?reinterpret_cast
często jest to broń z wyboru w przypadku zestawów nieprzezroczystych typów danych APISłuży
dynamic_cast
do konwertowania wskaźników / referencji w hierarchii dziedziczenia.Użyj
static_cast
do konwersji zwykłego typu.Służy
reinterpret_cast
do reinterpretacji wzorów bitowych na niskim poziomie. Używaj z dużą ostrożnością.Użyj
const_cast
do rzucaniaconst/volatile
. Unikaj tego, chyba że utkniesz przy użyciu niepoprawnego interfejsu API.źródło
(Wiele wyjaśnień teoretycznych i koncepcyjnych podano powyżej)
Poniżej kilka praktycznych przykładów, gdy użyłem static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Odnosi się to również do zrozumienia wyjaśnienia: http://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast:
dynamic_cast:
const_cast:
reinterpret_cast:
źródło
static_cast<char*>(&val)
?static_cast
działa tylko między typami z określonymi konwersjami, widoczną relacją według dziedziczenia lub do / zvoid *
. Na wszystko inne są inne obsady.reinterpret cast
na dowolnychar *
typ zezwala się na odczyt reprezentacji dowolnego obiektu - i jest to jeden z niewielu przypadków, w których to słowo kluczowe jest przydatne, a nie szalony generator implementacji / niezdefiniowane zachowanie. Ale nie jest to uważane za „normalną” konwersję, więc nie jest dozwolone (zwykle) bardzo konserwatywnestatic_cast
.Może to pomóc, jeśli znasz trochę elementów wewnętrznych ...
static_cast
static_cast
dla nich.A
naB
, konstruktorstatic_cast
wywołańB
przechodziA
jako param. AlternatywnieA
może mieć operator konwersji (tjA::operator B()
.). JeśliB
nie ma takiego konstruktora lubA
nie ma operatora konwersji, pojawia się błąd czasu kompilacji.A*
doB*
zawsze kończy się powodzeniem, jeśli A i B są w hierarchii dziedziczenia (lub nieważne), w przeciwnym razie wystąpi błąd kompilacji.A&
sięB&
.dynamic_cast
(Base*)
aby(Derived*)
może zakończyć się niepowodzeniem, jeśli wskaźnik nie jest w rzeczywistości typu pochodnej.A*
celuB*
, jeśli obsada jest nieprawidłowy następnie dynamic_cast powróci nullptr.A&
abyB&
jeśli obsada jest nieprawidłowy następnie dynamic_cast rzuci bad_cast wyjątek.const_cast
set<T>
który zwraca tylko jego elementy jako const, aby upewnić się, że nie zmienisz jego klucza. Jeśli jednak masz zamiar zmodyfikować niekluczowe elementy obiektu, powinno być w porządku. Możesz użyć const_cast, aby usunąć constness.T& SomeClass::foo()
a takżeconst T& SomeClass::foo() const
. Aby uniknąć powielania kodu, możesz zastosować const_cast, aby zwrócić wartość jednej funkcji z drugiej.reinterpret_cast
źródło
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
Dostajesz UB, co może skutkować awarią w czasie wykonywania, jeśli masz szczęście. 2. Odlewy dynamiczne można również stosować w odlewach krzyżowych. 3. Rzutowanie Const może w niektórych przypadkach powodować UB. Użyciemutable
może być lepszym wyborem do wdrożenia logicznej stałości.mutable
, castingiem krzyżowym itp.Czy to odpowiada na twoje pytanie?
Nigdy nie korzystałem
reinterpret_cast
i zastanawiam się, czy trafienie na skrzynkę, która tego potrzebuje, nie jest zapachem złego designu. W bazie kodu, nad którą pracuję,dynamic_cast
używa się dużo. Różnicastatic_cast
polega na tym, żedynamic_cast
sprawdza środowisko uruchomieniowe , które może (bezpieczniej) lub nie (więcej narzutu) być tym, czego chcesz (patrz msdn ).źródło
reinterpret_cast
do wydobywania fragmentów danych z tablicy. Na przykład, jeśli mamchar*
duży bufor pełen spakowanych danych binarnych, które muszę przejść i uzyskać pojedyncze prymitywy różnych typów. Coś w tym stylu:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
, nie ma zbyt wielu zastosowań.reinterpret_cast
używane tylko z jednego powodu. Widziałem dane surowego obiektu przechowywane w typie danych „obiektu blob” w bazie danych, a następnie, gdy dane są pobierane z bazy danych,reinterpret_cast
są używane do przekształcenia tych surowych danych w obiekt.Oprócz innych dotychczasowych odpowiedzi, tutaj jest nieoczywisty przykład, w którym
static_cast
nie jest wystarczający, więcreinterpret_cast
jest potrzebny. Załóżmy, że istnieje funkcja, która w parametrze wyjściowym zwraca wskaźniki do obiektów różnych klas (które nie dzielą wspólnej klasy podstawowej). Prawdziwym przykładem takiej funkcji jestCoCreateInstance()
(zobacz ostatni parametr, który w rzeczywistości jestvoid**
). Załóżmy, że żądasz określonej klasy obiektu od tej funkcji, więc z góry znasz typ wskaźnika (co często robisz dla obiektów COM). W tym przypadku nie można rzutować wskaźnik do wskaźnika do swojejvoid**
zestatic_cast
musiszreinterpret_cast<void**>(&yourPointer)
.W kodzie:
Jednak
static_cast
działa na prostych wskaźników (nie wskaźniki do wskaźników), więc powyższy kod może zostać przepisany w celu uniknięciareinterpret_cast
(w cenie dodatkową zmienną) w następujący sposób:źródło
&static_cast<void*>(pNetFwPolicy2)
zamiaststatic_cast<void**>(&pNetFwPolicy2)
?Podczas gdy inne odpowiedzi ładnie opisywały wszystkie różnice między rzutami w C ++, chciałbym dodać krótką notatkę, dlaczego nie powinieneś używać rzutów w stylu C
(Type) var
iType(var)
.Dla początkujących w C ++ rzutowania w stylu C wyglądają jak nadzbiór nad rzutowaniami C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) i ktoś może je preferować nad rzutami C ++ . W rzeczywistości obsada w stylu C jest nadzbiorem i jest krótsza do napisania.
Głównym problemem w obsadach w stylu C jest to, że ukrywają prawdziwą intencję twórców obsady. Rzutowania w stylu C mogą wykonywać praktycznie wszystkie typy rzutowania, od normalnie bezpiecznych rzutów wykonywanych przez static_cast <> () i dynamic_cast <> () do potencjalnie niebezpiecznych rzutowań, takich jak const_cast <> (), gdzie można zmienić modyfikator const, aby zmienne const można modyfikować i reinterpretować <cast (>), które mogą nawet ponownie interpretować wartości całkowite na wskaźniki.
Oto próbka.
Głównym powodem, dla którego dodano obsady C ++ do tego języka, było umożliwienie programistom wyjaśnienia swoich zamiarów - dlaczego zamierza to zrobić. Używając rzutowań w stylu C, które są w pełni poprawne w C ++, czynisz swój kod mniej czytelnym i bardziej podatnym na błędy, szczególnie dla innych programistów, którzy nie stworzyli twojego kodu. Aby twój kod był bardziej czytelny i wyraźny, zawsze powinieneś preferować rzutowania w C ++ niż rzutowania w stylu C.
Oto krótki cytat z książki Bjarne Stroustrup (autor C ++) The C ++ Programming Language 4. wydanie - strona 302.
źródło
Aby to zrozumieć, rozważmy poniższy fragment kodu:
Tylko linia (4) kompiluje się bez błędów. Tylko reinterpret_cast można użyć do konwersji wskaźnika na obiekt na wskaźnik na dowolny niepowiązany typ obiektu.
Należy zauważyć, że: dynamic_cast nie powiedzie się w czasie wykonywania, jednak w większości kompilatorów również nie uda się go skompilować, ponieważ w strukturze rzutowanego wskaźnika nie ma żadnych funkcji wirtualnych, co oznacza, że dynamic_cast będzie działał tylko ze wskaźnikami klasy polimorficznej .
Kiedy używać obsady C ++ :
źródło
static_cast
widok kontradynamic_cast
kontrareinterpret_cast
wewnętrzne na downcast / upcastW tej odpowiedzi chcę porównać te trzy mechanizmy na konkretnym przykładzie upcast / downcast i przeanalizować, co dzieje się z podstawowymi wskaźnikami / pamięcią / zestawem, aby uzyskać konkretne zrozumienie ich porównania.
Wierzę, że da to dobrą intuicję na temat różnic między tymi obsadami:
static_cast
: robi jedno przesunięcie adresu w czasie wykonywania (mały wpływ w czasie działania) i nie sprawdza bezpieczeństwa, czy downcast jest poprawny.dyanamic_cast
: robi to samo przesunięcie adresu jak w czasie wykonywaniastatic_cast
, ale także i kosztowną kontrolę bezpieczeństwa, czy downcast jest poprawny przy użyciu RTTI.Ta kontrola bezpieczeństwa pozwala zapytać, czy wskaźnik klasy bazowej jest określonego typu w czasie wykonywania, sprawdzając, czy zwrot
nullptr
wskazuje nieprawidłowy downcast.Dlatego jeśli twój kod nie jest w stanie tego sprawdzić
nullptr
i podjąć prawidłowej akcji nie przerywania, powinieneś użyćstatic_cast
zamiast dynamicznego rzutowania.Jeśli przerwanie jest jedyną czynnością, jaką może wykonać Twój kod, być może chcesz tylko włączyć
dynamic_cast
kompilacje debugowania (-NDEBUG
) i użyćstatic_cast
inaczej, np. Tak jak tutaj , aby nie spowalniać szybkiego uruchamiania.reinterpret_cast
: nic nie robi w czasie wykonywania, nawet przesunięcie adresu. Wskaźnik musi wskazywać dokładnie odpowiedni typ, nawet klasa podstawowa nie działa. Zasadniczo nie chcesz tego, chyba że zaangażowane są strumienie surowych bajtów.Rozważ następujący przykład kodu:
main.cpp
Kompiluj, uruchamiaj i dezasembluj za pomocą:
gdzie
setarch
jest używany do wyłączenia ASLR aby ułatwić porównanie przebiegów.Możliwe wyjście:
Teraz, jak wspomniano na stronie : https://en.wikipedia.org/wiki/Virtual_method_table, aby skutecznie wspierać wirtualne wywołania metod, struktura danych pamięci
D
musi wyglądać mniej więcej tak:Kluczowym faktem jest to, że struktura danych pamięci
D
zawiera w sobie strukturę pamięci zgodną ze strukturą wewnętrznąB1
iB2
wewnętrzną.Dlatego dochodzimy do krytycznego wniosku:
W ten sposób, gdy
D
zostanie przekazany do tablicy typów bazowych, rzutowany typ faktycznie oblicza to przesunięcie i wskazuje coś, co wygląda dokładnie tak, jak prawidłoweB2
w pamięci:poza tym, że ten ma
D
zamiast tego vtableB2
, dlatego wszystkie wirtualne połączenia działają transparentnie.Teraz możemy wreszcie wrócić do odlewania czcionek i analizy naszego konkretnego przykładu.
Z wyjścia standardowego widzimy:
Dlatego domyślnie
static_cast
wykonane tam poprawnie obliczyło przesunięcie od pełnejD
struktury danych w 0x7fffffffc930 doB2
podobnej, która jest w 0x7fffffffc940. Wnioskujemy również, że między 0x7fffffffc930 a 0x7fffffffc940 prawdopodobnie są toB1
dane i vtable.Następnie w sekcjach spuszczanych łatwo jest zrozumieć, dlaczego zawiodły nieprawidłowe i dlaczego:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: kompilator właśnie poszedł w górę 0x10 w bajtach czasu kompilacji, aby spróbować przejść od aB2
do zawierającegoD
Ale ponieważ
b2s[0]
nie byłoD
, wskazuje teraz na nieokreślony region pamięci.Demontaż to:
więc widzimy, że GCC:
D
co nie istniejedynamic_cast<D*>(b2s[0]) 0
: C ++ faktycznie stwierdził, że rzutowanie było nieprawidłowe i zwróciłnullptr
!Nie można tego zrobić w czasie kompilacji, a my potwierdzimy to z dezasemblacji:
Najpierw jest sprawdzanie NULL i zwraca NULL, jeśli einput ma wartość NULL.
W przeciwnym razie ustawia niektóre argumenty w RDX, RSI i RDI oraz wywołania
__dynamic_cast
.Nie mam teraz cierpliwości, aby dalej to analizować, ale jak powiedzieli inni, jedynym sposobem na to jest
__dynamic_cast
dostęp do niektórych dodatkowych struktur danych w pamięci RTTI, które reprezentują hierarchię klas.Dlatego musi zaczynać od
B2
wpisu dla tej tabeli, a następnie przejść tę hierarchię klas, aż stwierdzi, że vtable dlaD
typecast zb2s[0]
.Dlatego reinterpretacja obsady jest potencjalnie droga! Oto przykład, w którym łatka jednowarstwowa przekształcająca
dynamic_cast
astatic_cast
w złożony projekt skróciła czas działania o 33%! .reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
ten po prostu wierzy nam na ślepo: powiedzieliśmy, że jestD
adresb2s[1]
, a kompilator nie wykonuje obliczeń przesunięcia.Ale to źle, ponieważ D faktycznie ma 0x7fffffffc930, a co 0x7fffffffc940 to struktura podobna do B2 wewnątrz D! Więc dostęp do śmieci.
Możemy to potwierdzić z przerażającego
-O0
zestawu, który po prostu przesuwa wartość:Powiązane pytania:
Testowane na Ubuntu 18.04 amd64, GCC 7.4.0.
źródło