C++
Conversioni di tipo esplicito
Ricerca…
introduzione
Un'espressione può essere convertita o cast esplicita per digitare T
usando dynamic_cast<T>
, static_cast<T>
, reinterpret_cast<T>
, o const_cast<T>
, a seconda del tipo di cast desiderato.
C ++ supporta anche la notazione cast in stile funzione, T(expr)
e la notazione cast in stile C, (T)expr
.
Sintassi
- identificatore di tipo semplice
(
)
- identificatore di tipo semplice
(
elenco di espressioni)
- semplice-tipo-identificatore braced-init-list
- typename-specificifier
(
)
- typename-specificifier
(
expression-list)
- typename-specifier braced-init-list
-
dynamic_cast
<
id-tipo>
(
espressione)
-
static_cast
<
id-tipo>
(
espressione)
-
reinterpret_cast
<
id-tipo>
(
espressione)
-
const_cast
<
id-tipo>
(
espressione)
-
(
type-id)
cast-expression
Osservazioni
Tutte e sei le annotazioni sul cast hanno una cosa in comune:
- Il casting su un tipo di riferimento lvalue, come in
dynamic_cast<Derived&>(base)
, produce un lvalue. Pertanto, quando si vuole fare qualcosa con lo stesso oggetto ma trattarlo come un tipo diverso, si passa a un tipo di riferimento lvalue. - La trasmissione a un tipo di riferimento di rvalue, come in
static_cast<string&&>(s)
, produce un valore rvalue. - Il casting su un tipo non di riferimento, come in
(int)x
, produce un valore, che può essere considerato come una copia del valore da trasmettere, ma con un tipo diverso dall'originale.
La parola chiave reinterpret_cast
è responsabile dell'esecuzione di due diversi tipi di conversioni "non sicure":
- Le conversioni "type punning" , che possono essere utilizzate per accedere alla memoria di un tipo come se fosse di un tipo diverso.
- Conversioni tra tipi interi e tipi di puntatore , in entrambe le direzioni.
La parola chiave static_cast
può eseguire una varietà di conversioni diverse:
Qualsiasi conversione che può essere eseguita tramite un'inizializzazione diretta, incluse conversioni e conversioni implicite che chiamano una funzione di costruzione o conversione esplicita. Vedi qui e qui per maggiori dettagli.
Per
void
, che scarta il valore dell'espressione.// on some compilers, suppresses warning about x being unused static_cast<void>(x);
Tra i tipi aritmetici e di enumerazione e tra i diversi tipi di enumerazione. Vedi conversioni enum
Da puntatore a membro della classe derivata, puntatore al membro della classe base. I tipi a cui punta devono corrispondere. Vedi la conversione da derivata a base per i puntatori ai membri
- Da un lvalue a un valore x, come in
std::move
. Vedi spostare la semantica .
Conversione da base a derivata
Un puntatore alla classe base può essere convertito in un puntatore alla classe derivata usando static_cast
. static_cast
non esegue alcun controllo di runtime e può portare a comportamenti non definiti quando il puntatore non punta effettivamente al tipo desiderato.
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
Allo stesso modo, un riferimento alla classe base può essere convertito in un riferimento alla classe derivata usando 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
Se il tipo di origine è polimorfico, dynamic_cast
può essere utilizzato per eseguire una conversione da base a derivata. Esegue un controllo in fase di esecuzione e l'errore è recuperabile invece di produrre un comportamento non definito. Nel caso del puntatore, un puntatore nullo viene restituito in caso di errore. Nel caso di riferimento, viene generata un'eccezione in caso di errore di tipo std::bad_cast
(o una classe derivata da 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
Gettare via la costanza
Un puntatore a un oggetto const può essere convertito in un puntatore all'oggetto non-const usando la parola chiave const_cast
. Qui usiamo const_cast
per chiamare una funzione che non è const-correct. Accetta solo un argomento non-const char*
anche se non scrive mai attraverso il puntatore:
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
al tipo di riferimento può essere utilizzato per convertire un valore lovalificato const-value in un valore non const-qualificato.
const_cast
è pericoloso perché rende impossibile al sistema di tipo C ++ impedirti di tentare di modificare un oggetto const. Ciò comporta un comportamento indefinito.
const int x = 123;
int& mutable_x = const_cast<int&>(x);
mutable_x = 456; // may compile, but produces *undefined behavior*
Digitare la conversione punitiva
Un puntatore (riferimento) a un tipo di oggetto può essere convertito in un puntatore (riferimento) in qualsiasi altro tipo di oggetto utilizzando reinterpret_cast
. Questo non chiama costruttori o funzioni di conversione.
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)
Il risultato di reinterpret_cast
rappresenta lo stesso indirizzo dell'operando, a condizione che l'indirizzo sia allineato in modo appropriato per il tipo di destinazione. Altrimenti, il risultato non è specificato.
int x = 42;
char& r = reinterpret_cast<char&>(x);
const void* px = &x;
const void* pr = &r;
assert(px == pr); // should never fire
Il risultato di reinterpret_cast
non è specificato, tranne che un puntatore (riferimento) sopravviverà a un round trip dal tipo di origine al tipo di destinazione e viceversa, purché il requisito di allineamento del tipo di destinazione non sia più rigido di quello del tipo di origine.
int x = 123;
unsigned int& r1 = reinterpret_cast<unsigned int&>(x);
int& r2 = reinterpret_cast<int&>(r1);
r2 = 456; // sets x to 456
Sulla maggior parte delle implementazioni, reinterpret_cast
non cambia l'indirizzo, ma questo requisito non è stato standardizzato fino a C ++ 11.
reinterpret_cast
può anche essere utilizzato per convertire da un tipo di membro puntatore a membro di dati a un altro o un tipo di funzione da puntatore a membro a un altro.
L'uso di reinterpret_cast
è considerato pericoloso perché la lettura o la scrittura attraverso un puntatore o un riferimento ottenuto utilizzando reinterpret_cast
può attivare un comportamento indefinito quando i tipi di origine e destinazione non sono correlati.
Conversione tra puntatore e intero
Un puntatore oggetto (incluso void*
) o un puntatore funzione può essere convertito in un tipo intero usando reinterpret_cast
. Questo verrà compilato solo se il tipo di destinazione è abbastanza lungo. Il risultato è definito dall'implementazione e tipicamente restituisce l'indirizzo numerico del byte in memoria a cui puntano i puntatori del puntatore.
In genere, long
o unsigned long
è abbastanza lungo da contenere qualsiasi valore del puntatore, ma questo non è garantito dallo standard.
Se i tipi std::intptr_t
e std::uintptr_t
esistono, sono garantiti per essere abbastanza lunghi da contenere un void*
(e quindi qualsiasi puntatore al tipo di oggetto). Tuttavia, non è garantito che siano sufficientemente lunghi da contenere un puntatore a funzione.
Allo stesso modo, reinterpret_cast
può essere utilizzato per convertire un tipo intero in un tipo di puntatore. Anche in questo caso il risultato è definito dall'implementazione, ma è garantito che il valore di un puntatore non venga modificato da un round trip attraverso un tipo intero. Lo standard non garantisce che il valore zero venga convertito in un puntatore nullo.
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...
Conversione tramite costruttore esplicito o funzione di conversione esplicita
Una conversione che implica il richiamo di una funzione di costruzione o conversione esplicita non può essere eseguita implicitamente. Possiamo richiedere che la conversione venga eseguita esplicitamente utilizzando static_cast
. Il significato è lo stesso di quello di un'inizializzazione diretta, tranne per il fatto che il risultato è temporaneo.
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
}
Conversione implicita
static_cast
può eseguire qualsiasi conversione implicita. Questo uso di static_cast
può talvolta essere utile, come nei seguenti esempi:
Quando si passano argomenti a puntini di sospensione, il tipo di argomento "previsto" non è noto staticamente, quindi non si verificherà alcuna conversione implicita.
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);
Senza la conversione di tipo esplicita, un
double
oggetto verrebbe passato ai puntini di sospensione e si verificherebbe un comportamento non definito.Un operatore di assegnazione classe derivato può chiamare un operatore di assegnazione classe base in questo modo:
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; } };
Conversioni Enum
static_cast
può convertire da un numero intero o un tipo a virgola mobile a un tipo di enumerazione (con ambito o senza ambito) e viceversa. Può anche convertire tra tipi di enumerazione.
- La conversione da un tipo di enumerazione senza ambito a un tipo aritmetico è una conversione implicita; è possibile, ma non necessario, usare
static_cast
.
Quando un tipo di enumerazione dell'ambito viene convertito in un tipo aritmetico:
- Se il valore dell'enumerazione può essere rappresentato esattamente nel tipo di destinazione, il risultato è quel valore.
- Altrimenti, se il tipo di destinazione è un tipo intero, il risultato non è specificato.
- Altrimenti, se il tipo di destinazione è un tipo a virgola mobile, il risultato è uguale a quello della conversione al tipo sottostante e quindi al tipo a virgola mobile.
Esempio:
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
Quando un numero intero o un tipo di enumerazione viene convertito in un tipo di enumerazione:
- Se il valore originale è compreso nell'intervallo dell'enumerazione di destinazione, il risultato è tale valore. Si noti che questo valore potrebbe non essere uguale a tutti gli enumeratori.
- In caso contrario, il risultato non è specificato (<= C ++ 14) o non definito (> = C ++ 17).
Esempio:
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
Quando un tipo a virgola mobile viene convertito in un tipo di enumerazione, il risultato è lo stesso della conversione al tipo sottostante dell'enumerazione e quindi al tipo enum.
enum Direction { UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3, }; Direction d = static_cast<Direction>(3.14); // d is RIGHT
Derivato per basare la conversione per i puntatori ai membri
Un puntatore al membro della classe derivata può essere convertito in un puntatore al membro della classe base utilizzando static_cast
. I tipi a cui punta devono corrispondere.
Se l'operando è un puntatore nullo al valore del membro, il risultato è anche un puntatore nullo al valore del membro.
Altrimenti, la conversione è valida solo se il membro a cui punta l'operando esiste effettivamente nella classe di destinazione, o se la classe di destinazione è una classe di base o derivata della classe che contiene il membro puntato dall'operando. static_cast
non verifica la validità. Se la conversione non è valida, il comportamento non è definito.
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 * a T *
In C ++, void*
non può essere convertito implicitamente in T*
dove T
è un tipo di oggetto. Invece, static_cast
dovrebbe essere usato per eseguire la conversione esplicitamente. Se l'operando punta effettivamente su un oggetto T
, il risultato punta a quell'oggetto. Altrimenti, il risultato non è specificato.
Anche se l'operando non punta a un oggetto T
, finché l'operando punta a un byte il cui indirizzo è correttamente allineato per il tipo T
, il risultato della conversione punta allo stesso byte.
// 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
Casting in stile C.
Il cast di C-Style può essere considerato il cast "Best effort" e viene chiamato così come è l'unico cast che può essere usato in C. La sintassi per questo cast è (NewType)variable
.
Ogni volta che questo cast viene usato, usa uno dei seguenti cast di c ++ (in ordine):
-
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))
Il casting funzionale è molto simile, anche se come alcune restrizioni come risultato della sua sintassi: NewType(expression)
. Di conseguenza, è possibile trasmettere solo i tipi senza spazi.
È meglio usare il nuovo cast di c ++, perché è più leggibile e può essere facilmente individuato ovunque all'interno di un codice sorgente C ++ e gli errori verranno rilevati in fase di compilazione, invece in fase di esecuzione.
Poiché questo cast può risultare in reinterpret_cast
non intenzionale, è spesso considerato pericoloso.