C++
Jawne konwersje typu
Szukaj…
Wprowadzenie
Wyrażenie można jawnie przekonwertować lub rzutować na typ T
za pomocą static_cast<T>
dynamic_cast<T>
, static_cast<T>
, reinterpret_cast<T>
const_cast<T>
lub const_cast<T>
, w zależności od zamierzonego typu rzutowania.
C ++ obsługuje również notację rzutowania w stylu funkcyjnym, T(expr)
i notację rzutowania w stylu C, (T)expr
.
Składnia
- specyfikator typu prostego
(
)
- specyfikator typu prostego
(
lista wyrażeń)
- specyfikator typu prostego braced-init-list
- typename-specifier
(
)
- typename-specifier
(
lista-wyrażeń)
- typename-specifier braced-init-list
-
dynamic_cast
<
type-id>
(
wyrażenie)
-
static_cast
<
typ-id>
(
wyrażenie)
-
reinterpret_cast
<
typ-id>
(
wyrażenie)
-
const_cast
<
typ-id>
(
wyrażenie)
-
(
typ-id)
rzutowane wyrażenie
Uwagi
Wszystkie sześć notacji rzutowych ma jedną wspólną cechę:
- Rzutowanie na typ odwołania do wartości, jak w
dynamic_cast<Derived&>(base)
, daje wartość. Dlatego jeśli chcesz zrobić coś z tym samym obiektem, ale traktować go jako inny typ, możesz przerzucić na typ odwołania do wartości. - Rzutowanie na typ odwołania do wartości, jak w
static_cast<string&&>(s)
, daje wartość. - Rzutowanie na typ niereferencyjny, jak w
(int)x
, daje wartość, którą można uznać za kopię rzutowanej wartości, ale z innym typem niż oryginał.
reinterpret_cast
kluczowe reinterpret_cast
odpowiada za wykonanie dwóch różnych rodzajów „niebezpiecznych” konwersji:
- Konwersje typu „punning” , które mogą być wykorzystane do uzyskania dostępu do pamięci jednego typu, tak jakby był innego typu.
- Konwersje między typami liczb całkowitych a typami wskaźników w obu kierunkach.
static_cast
kluczowe static_cast
może wykonywać wiele różnych konwersji:
Konwersja do pochodnych
Każda konwersja, którą można wykonać przez bezpośrednią inicjalizację, w tym zarówno konwersje niejawne, jak i konwersje, które wywołują jawnego konstruktora lub funkcję konwersji. Zobacz tutaj i tutaj, aby uzyskać więcej informacji.
Aby
void
, co odrzuca wartość wyrażenia.// on some compilers, suppresses warning about x being unused static_cast<void>(x);
Między typami arytmetyki i wyliczeń oraz między różnymi typami wyliczeń. Zobacz konwersje enum
Od wskaźnika do elementu klasy pochodnej, od wskaźnika do elementu klasy podstawowej. Wskazane typy muszą się zgadzać. Zobacz konwersję pochodną do podstawy dla wskaźników na pręty
- Od wartości do wartości x, jak w
std::move
. Zobacz semantykę przenoszenia .
Konwersja bazy do pochodnej
Wskaźnik do klasy bazowej można przekonwertować na wskaźnik do klasy pochodnej za pomocą static_cast
. static_cast
nie wykonuje żadnej kontroli w czasie wykonywania i może prowadzić do nieokreślonego zachowania, gdy wskaźnik faktycznie nie wskazuje pożądanego typu.
struct Base {};
struct Derived : Base {};
Derived d;
Base* p1 = &d;
Derived* p2 = p1; // error; cast required
Derived* p3 = static_cast<Derived*>(p1); // OK; p2 now points to Derived object
Base b;
Base* p4 = &b;
Derived* p5 = static_cast<Derived*>(p4); // undefined behaviour since p4 does not
// point to a Derived object
Podobnie odwołanie do klasy bazowej można przekształcić w odwołanie do klasy pochodnej za pomocą static_cast
.
struct Base {};
struct Derived : Base {};
Derived d;
Base& r1 = d;
Derived& r2 = r1; // error; cast required
Derived& r3 = static_cast<Derived&>(r1); // OK; r3 now refers to Derived object
Jeśli typ źródła jest polimorficzny, można użyć dynamic_cast
do wykonania konwersji bazy na pochodną. Wykonuje kontrolę w czasie wykonywania, a awarię można odzyskać zamiast tworzyć niezdefiniowane zachowanie. W przypadku wskaźnika wskaźnik zerowy jest zwracany po awarii. W przypadku odniesienia generowany jest wyjątek w przypadku niepowodzenia typu std::bad_cast
(lub klasy pochodnej od std::bad_cast
).
struct Base { virtual ~Base(); }; // Base is polymorphic
struct Derived : Base {};
Base* b1 = new Derived;
Derived* d1 = dynamic_cast<Derived*>(b1); // OK; d1 points to Derived object
Base* b2 = new Base;
Derived* d2 = dynamic_cast<Derived*>(b2); // d2 is a null pointer
Odrzucanie ciągłości
Wskaźnik do obiektu const można przekształcić w wskaźnik do obiektu non-const przy użyciu słowa kluczowego const_cast
. W tym przypadku używamy const_cast
aby wywołać funkcję, która nie jest const_cast
. Akceptuje tylko nietrwały argument char*
nawet jeśli nigdy nie pisze przez wskaźnik:
void bad_strlen(char*);
const char* s = "hello, world!";
bad_strlen(s); // compile error
bad_strlen(const_cast<char*>(s)); // OK, but it's better to make bad_strlen accept const char*
const_cast
na typ referencyjny może być wykorzystany do konwersji wartości const_cast
const na wartość niekwalifikowaną do const.
const_cast
jest niebezpieczny, ponieważ uniemożliwia systemowi typu C ++ uniemożliwienie próby modyfikacji obiektu const. Takie postępowanie powoduje niezdefiniowane zachowanie.
const int x = 123;
int& mutable_x = const_cast<int&>(x);
mutable_x = 456; // may compile, but produces *undefined behavior*
Wpisz konwersję znakowania
Wskaźnik (odpowiednio odniesienie) do typu obiektu można przekształcić w wskaźnik (odpowiednio odniesienie) do dowolnego innego typu obiektu za pomocą reinterpret_cast
. Nie wywołuje to żadnych konstruktorów ani funkcji konwersji.
int x = 42;
char* p = static_cast<char*>(&x); // error: static_cast cannot perform this conversion
char* p = reinterpret_cast<char*>(&x); // OK
*p = 'z'; // maybe this modifies x (see below)
Wynik reinterpret_cast
reprezentuje ten sam adres co operand, pod warunkiem, że adres jest odpowiednio wyrównany dla typu miejsca docelowego. W przeciwnym razie wynik nie jest określony.
int x = 42;
char& r = reinterpret_cast<char&>(x);
const void* px = &x;
const void* pr = &r;
assert(px == pr); // should never fire
Wynik reinterpret_cast
jest określony, z wyjątkiem tego, że wskaźnik (lub odniesienie) przetrwa podróż w obie strony od typu źródłowego do typu docelowego iz powrotem, o ile wymóg wyrównania typu docelowego nie jest bardziej rygorystyczny niż typ źródłowy.
int x = 123;
unsigned int& r1 = reinterpret_cast<unsigned int&>(x);
int& r2 = reinterpret_cast<int&>(r1);
r2 = 456; // sets x to 456
W większości implementacji reinterpret_cast
nie zmienia adresu, ale wymaganie to nie było znormalizowane do C ++ 11.
reinterpret_cast
może być również używany do konwersji z jednego typu wskaźnika na element członkowski danych na inny lub z jednego typu wskaźnika na element członkowski na inny.
Użycie reinterpret_cast
jest uważane za niebezpieczne, ponieważ odczyt lub zapis przez wskaźnik lub odniesienie uzyskane za pomocą reinterpret_cast
może wyzwalać niezdefiniowane zachowanie, gdy typy źródłowe i docelowe nie są ze sobą powiązane.
Konwersja między wskaźnikiem a liczbą całkowitą
Wskaźnik obiektu (w tym void*
) lub wskaźnik funkcji można przekonwertować na typ całkowity za pomocą reinterpret_cast
. Skompiluje się tylko, jeśli typ miejsca docelowego jest wystarczająco długi. Wynik jest zdefiniowany w implementacji i zwykle daje adres numeryczny bajtu w pamięci, na który wskazuje wskaźnik.
Zazwyczaj long
lub unsigned long
jest wystarczająco długi, aby pomieścić dowolną wartość wskaźnika, ale nie jest to gwarantowane przez standard.
Jeśli istnieją typy std::intptr_t
i std::uintptr_t
, na std::uintptr_t
są one wystarczająco długie, aby pomieścić void*
(a zatem dowolny wskaźnik do typu obiektu). Nie gwarantuje się jednak, że są wystarczająco długie, aby pomieścić wskaźnik funkcji.
Podobnie, reinterpret_cast
może być użyty do konwersji typu liczby całkowitej na typ wskaźnika. Ponownie wynik jest zdefiniowany w implementacji, ale gwarantuje, że wartość wskaźnika pozostanie niezmieniona przez podróż w obie strony przez liczbę całkowitą. Norma nie gwarantuje, że wartość zero zostanie przekonwertowana na wskaźnik zerowy.
void register_callback(void (*fp)(void*), void* arg); // probably a C API
void my_callback(void* x) {
std::cout << "the value is: " << reinterpret_cast<long>(x); // will probably compile
}
long x;
std::cin >> x;
register_callback(my_callback,
reinterpret_cast<void*>(x)); // hopefully this doesn't lose information...
Konwersja przez jawnego konstruktora lub jawną funkcję konwersji
Konwersja, która wymaga wywołania jawnego konstruktora lub funkcji konwersji, nie może być wykonana pośrednio. Możemy zażądać jawnej konwersji za pomocą static_cast
. Znaczenie jest takie samo jak w przypadku bezpośredniej inicjalizacji, z wyjątkiem tego, że wynik jest tymczasowy.
class C {
std::unique_ptr<int> p;
public:
explicit C(int* p) : p(p) {}
};
void f(C c);
void g(int* p) {
f(p); // error: C::C(int*) is explicit
f(static_cast<C>(p)); // ok
f(C(p)); // equivalent to previous line
C c(p); f(c); // error: C is not copyable
}
Niejawna konwersja
static_cast
może wykonać dowolną niejawną konwersję. Takie użycie static_cast
może czasami być przydatne, na przykład w następujących przykładach:
Podczas przekazywania argumentów do elipsy „oczekiwany” typ argumentu nie jest statycznie znany, więc nie nastąpi niejawna konwersja.
const double x = 3.14; printf("%d\n", static_cast<int>(x)); // prints 3 // printf("%d\n", x); // undefined behaviour; printf is expecting an int here // alternative: // const int y = x; printf("%d\n", y);
Bez jawnej konwersji typu
double
obiekt zostałby przekazany do elipsy i wystąpiłoby niezdefiniowane zachowanie.Operator przypisania klas pochodnych może wywoływać operatora przypisania klas bazowych w następujący sposób:
struct Base { /* ... */ }; struct Derived : Base { Derived& operator=(const Derived& other) { static_cast<Base&>(*this) = other; // alternative: // Base& this_base_ref = *this; this_base_ref = other; } };
Przeliczanie enum
static_cast
może konwertować z typu liczb całkowitych lub zmiennoprzecinkowych na typ wyliczenia (zarówno o zasięgu, jak i bez zakresu) i odwrotnie. Może także konwertować typy wyliczeń.
- Konwersja z nieokreślonego typu wyliczenia na typ arytmetyczny jest konwersją niejawną; możliwe jest, ale nie konieczne, użycie
static_cast
.
Gdy typ wyliczenia o zasięgu zostanie przekonwertowany na typ arytmetyczny:
- Jeśli wartość wyliczenia może być reprezentowana dokładnie w typie docelowym, wynikiem jest ta wartość.
- W przeciwnym razie, jeśli typ docelowy jest liczbą całkowitą, wynik nie jest określony.
- W przeciwnym razie, jeśli typem docelowym jest typ zmiennoprzecinkowy, wynik jest taki sam jak w przypadku konwersji na typ bazowy, a następnie na typ zmiennoprzecinkowy.
Przykład:
enum class Format { TEXT = 0, PDF = 1000, OTHER = 2000, }; Format f = Format::PDF; int a = f; // error int b = static_cast<int>(f); // ok; b is 1000 char c = static_cast<char>(f); // unspecified, if 1000 doesn't fit into char double d = static_cast<double>(f); // d is 1000.0... probably
Kiedy typ liczbowy lub typ wyliczenia jest konwertowany na typ wyliczenia:
- Jeśli oryginalna wartość mieści się w zakresie wyliczenia docelowego, wynikiem jest ta wartość. Zauważ, że ta wartość może być nierówna dla wszystkich modułów wyliczających.
- W przeciwnym razie wynik jest nieokreślony (<= C ++ 14) lub niezdefiniowany (> = C ++ 17).
Przykład:
enum Scale { SINGLE = 1, DOUBLE = 2, QUAD = 4 }; Scale s1 = 1; // error Scale s2 = static_cast<Scale>(2); // s2 is DOUBLE Scale s3 = static_cast<Scale>(3); // s3 has value 3, and is not equal to any enumerator Scale s9 = static_cast<Scale>(9); // unspecified value in C++14; UB in C++17
Kiedy typ zmiennoprzecinkowy jest konwertowany na typ wyliczenia, wynik jest taki sam jak konwersja na typ wyliczeniowy, a następnie na typ wyliczeniowy.
enum Direction { UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3, }; Direction d = static_cast<Direction>(3.14); // d is RIGHT
Pochodzi z konwersji podstawowej wskaźników do członków
Wskaźnik do elementu klasy pochodnej można przekonwertować na wskaźnik do elementu klasy podstawowej za pomocą static_cast
. Wskazane typy muszą się zgadzać.
Jeśli operand jest pustym wskaźnikiem wartości elementu, wynikiem jest również zerowy wskaźnik wartości elementu.
W przeciwnym razie konwersja jest ważna tylko wtedy, gdy element wskazany przez operand faktycznie istnieje w klasie docelowej lub jeśli klasa docelowa jest klasą bazową lub pochodną klasy zawierającej element wskazany przez operand. static_cast
nie sprawdza poprawności. Jeśli konwersja nie jest prawidłowa, zachowanie jest niezdefiniowane.
struct A {};
struct B { int x; };
struct C : A, B { int y; double z; };
int B::*p1 = &B::x;
int C::*p2 = p1; // ok; implicit conversion
int B::*p3 = p2; // error
int B::*p4 = static_cast<int B::*>(p2); // ok; p4 is equal to p1
int A::*p5 = static_cast<int A::*>(p2); // undefined; p2 points to x, which is a member
// of the unrelated class B
double C::*p6 = &C::z;
double A::*p7 = static_cast<double A::*>(p6); // ok, even though A doesn't contain z
int A::*p8 = static_cast<int A::*>(p6); // error: types don't match
void * do T *
W C ++ void*
nie może być niejawnie konwertowany na T*
gdzie T
jest typem obiektu. Zamiast tego należy użyć static_cast
do jawnego wykonania konwersji. Jeśli operand faktycznie wskazuje na obiekt T
, wynik wskazuje na ten obiekt. W przeciwnym razie wynik nie jest określony.
Nawet jeśli operand nie wskazuje obiektu T
, o ile operand wskazuje bajt, którego adres jest odpowiednio wyrównany dla typu T
, wynik konwersji wskazuje na ten sam bajt.
// allocating an array of 100 ints, the hard way
int* a = malloc(100*sizeof(*a)); // error; malloc returns void*
int* a = static_cast<int*>(malloc(100*sizeof(*a))); // ok
// int* a = new int[100]; // no cast needed
// std::vector<int> a(100); // better
const char c = '!';
const void* p1 = &c;
const char* p2 = p1; // error
const char* p3 = static_cast<const char*>(p1); // ok; p3 points to c
const int* p4 = static_cast<const int*>(p1); // unspecified in C++03;
// possibly unspecified in C++11 if
// alignof(int) > alignof(char)
char* p5 = static_cast<char*>(p1); // error: casting away constness
Odlewanie w stylu C.
Rzutowanie w stylu C można uznać za rzutowanie „najlepszego wysiłku” i nazywa się tak, ponieważ jest to jedyne rzutowanie, które można zastosować w C. Składnia tego rzutowania jest (NewType)variable
.
Ilekroć ta obsada jest używana, używa jednego z następujących rzutowań c ++ (w kolejności):
-
const_cast<NewType>(variable)
-
static_cast<NewType>(variable)
-
const_cast<NewType>(static_cast<const NewType>(variable))
-
reinterpret_cast<const NewType>(variable)
-
const_cast<NewType>(reinterpret_cast<const NewType>(variable))
Rzutowanie funkcjonalne jest bardzo podobne, choć jako kilka ograniczeń ze względu na jego składnię: NewType(expression)
. W rezultacie można rzutować tylko na typy bez spacji.
Lepiej jest użyć nowej obsady c ++, ponieważ jest bardziej czytelna i można ją łatwo zauważyć w dowolnym miejscu kodu źródłowego C ++, a błędy zostaną wykryte w czasie kompilacji, a nie w czasie wykonywania.
Ponieważ ta obsada może spowodować niezamierzony reinterpret_cast
, często uważa się ją za niebezpieczną.