Perché usare static_cast <int> (x) invece di (int) x?


667

Ho sentito che la static_castfunzione dovrebbe essere preferita al casting in stile C o semplice. È vero? Perché?


36
Obiezione il tuo onore, chiesto e risposto .
Graeme Perrow,

25
Non sono d'accordo, quest'altra domanda riguardava la descrizione delle differenze tra i lanci introdotti in C ++. Questa domanda riguarda la reale utilità di static_cast, che è leggermente diversa.
Vincent Robert,

2
Potremmo certamente unire le due domande, ma ciò che dovremmo preservare da questa discussione è il vantaggio di usare le funzioni rispetto al casting in stile C, che è attualmente menzionato solo in una risposta di una riga nell'altra discussione, senza voti .
Tommy Herbert,

6
Questa domanda riguarda i tipi "incorporati", come int, mentre quella relativa ai tipi di classe. Sembra una differenza abbastanza significativa da meritare una spiegazione separata.

9
static_cast è in realtà un operatore, non una funzione.
ThomasMcLeod,

Risposte:


633

La ragione principale è che classici calchi C non fanno distinzione tra ciò che noi chiamiamo static_cast<>(), reinterpret_cast<>(), const_cast<>(), e dynamic_cast<>(). Queste quattro cose sono completamente diverse.

A static_cast<>()è di solito sicuro. Esiste una conversione valida nella lingua o un costruttore appropriato che lo rende possibile. L'unica volta che è un po 'rischioso è quando ti abbassi a una classe ereditata; devi assicurarti che l'oggetto sia effettivamente il discendente che asserisci di essere, in modo esterno al linguaggio (come una bandiera nell'oggetto). A dynamic_cast<>()è sicuro fintanto che il risultato è selezionato (puntatore) o viene presa in considerazione una possibile eccezione (riferimento).

Una reinterpret_cast<>()(o una const_cast<>()) d'altra parte è sempre pericolosa. Dici al compilatore: "fidati di me: so che questo non sembra un foo(sembra che non sia mutabile), ma lo è".

Il primo problema è che è quasi impossibile dire quale accadrà in un cast in stile C senza guardare pezzi di codice grandi e dispersi e conoscere tutte le regole.

Supponiamo che:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Ora, questi due sono compilati allo stesso modo:

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

Tuttavia, vediamo questo codice quasi identico:

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!!!

Come puoi vedere, non esiste un modo semplice per distinguere tra le due situazioni senza sapere molto su tutte le classi coinvolte.

Il secondo problema è che i cast di tipo C sono troppo difficili da individuare. In espressioni complesse può essere molto difficile vedere i cast in stile C. È praticamente impossibile scrivere uno strumento automatizzato che deve individuare i cast in stile C (ad esempio uno strumento di ricerca) senza un front-end del compilatore C ++ completo. D'altra parte, è facile cercare "static_cast <" o "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.

Ciò significa che non solo i cast in stile C sono più pericolosi, ma è molto più difficile trovarli tutti per assicurarsi che siano corretti.


30
Non dovresti usare static_castper abbattere una gerarchia ereditaria, ma piuttosto dynamic_cast. Ciò restituirà il puntatore null o un puntatore valido.
David Thornley,

41
@ David Thornley: sono d'accordo, di solito. Penso di aver indicato le avvertenze da utilizzare static_castin quella situazione. dynamic_castpotrebbe essere più sicuro, ma non è sempre l'opzione migliore. A volte sai che un puntatore punta a un dato sottotipo, per mezzo opaco al compilatore, e a static_castè più veloce. In almeno alcuni ambienti, dynamic_castrichiede il supporto del compilatore opzionale e i costi di runtime (abilitazione di RTTI) e potresti non volerlo abilitare solo per un paio di controlli che puoi fare tu stesso. RTTI di C ++ è solo una possibile soluzione al problema.
Euro Micelli,

18
Il tuo reclamo sui cast di C è falso. Tutti i cast C sono conversioni di valore, approssimativamente paragonabili a C ++ static_cast. L'equivalente C di reinterpret_castè *(destination_type *)&, cioè prendere l'indirizzo dell'oggetto, lanciare quell'indirizzo su un puntatore a un tipo diverso e quindi dereferenziare. Tranne nel caso di tipi di carattere o di alcuni tipi di struttura per i quali C definisce il comportamento di questo costrutto, generalmente si ottiene un comportamento indefinito in C.
R .. GitHub FERMA AIUTANDO ICE

11
La tua bella risposta si rivolge al corpo del post. Stavo cercando una risposta al titolo "perché usare static_cast <int> (x) invece di (int) x". Cioè, per il tipo int(e intsolo), perché usare static_cast<int>vs. (int)come unico vantaggio sembra essere con variabili di classe e puntatori. Richiedi di approfondire questo.
chux - Ripristina Monica il

31
@chux, per int dynamic_castnon si applica, ma tutti gli altri motivi sono validi . Ad esempio: supponiamo che vsia un parametro di funzione dichiarato come float, quindi lo (int)vè static_cast<int>(v). Ma se si modifica il parametro in float*, (int)vdiventa tranquillamente reinterpret_cast<int>(v)mentre static_cast<int>(v)è illegale e correttamente catturato dal compilatore.
Euro Micelli,

115

Un consiglio pragmatico: puoi cercare facilmente la parola chiave static_cast nel tuo codice sorgente se prevedi di riordinare il progetto.


3
puoi cercare usando le parentesi anche se come "(int)" ma una buona risposta e un motivo valido per usare il cast in stile C ++.
Mike,

4
@Mike che troverà falsi positivi: una dichiarazione di funzione con un singolo intparametro.
Nathan Osman,

1
Questo può dare falsi negativi: se stai cercando una base di codice in cui non sei l'unico autore, non troverai cast in stile C che altri potrebbero aver introdotto per alcuni motivi.
Ruslan,

7
In che modo questo aiuterebbe a riordinare il progetto?
Bilow,

Non dovresti cercare static_cast, perché è molto probabilmente quello corretto. Vuoi filtrare static_cast, mentre cerchi reinterpret_cast, const_cast e forse anche dynamic_cast, poiché questi indicherebbero luoghi che possono essere riprogettati. C-cast si mescola tutti insieme e non ti dà il motivo del casting.
Dragan,

78

In breve :

  1. static_cast<>() ti dà la possibilità di compilare il tempo di verifica, il cast di C-Style no.
  2. static_cast<>()può essere individuato facilmente ovunque all'interno di un codice sorgente C ++; al contrario, il cast di C_Style è più difficile da individuare.
  3. Le intenzioni vengono trasmesse molto meglio usando i cast C ++.

Più spiegazione :

Il cast statico esegue conversioni tra tipi compatibili . È simile al cast in stile C, ma è più restrittivo. Ad esempio, il cast in stile C consentirebbe a un puntatore intero di puntare a un carattere.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Poiché ciò comporta un puntatore a 4 byte che punta a 1 byte di memoria allocata, la scrittura su questo puntatore provocherà un errore di runtime o sovrascriverà della memoria adiacente.

*p = 5; // run-time error: stack corruption

Contrariamente al cast in stile C, il cast statico consentirà al compilatore di verificare la compatibilità dei tipi di dati puntatore e puntatore, il che consente al programmatore di rilevare questa assegnazione errata del puntatore durante la compilazione.

int *q = static_cast<int*>(&c); // compile-time error

Maggiori informazioni su:
Qual è la differenza tra static_cast <> e il cast in stile C
e il
cast regolare rispetto a static_cast vs. dynamic_cast


21
Non sono d'accordo che static_cast<>()sia più leggibile. Voglio dire, a volte lo è, ma la maggior parte delle volte - specialmente sui tipi interi di base - è solo orribilmente e inutilmente prolisso. Ad esempio: questa è una funzione che scambia i byte di una parola a 32 bit. Sarebbe quasi impossibile leggere usando i static_cast<uint##>()cast, ma è abbastanza facile da capire usando i (uint##)cast. Immagine del codice: imgur.com/NoHbGve
Todd Lehman il

3
@ToddLehman: Grazie, ma non ho detto alwaysneanche. (ma la maggior parte delle volte sì) Ci sono sicuramente casi in cui il cast in stile c è molto più leggibile. Questo è uno dei motivi per cui il casting in stile c è ancora attivo e attivo in c ++ imho. :) A proposito, è stato un bell'esempio
Rika,

8
Il codice @ToddLehman in quell'immagine usa due cast concatenati ( (uint32_t)(uint8_t)) per ottenere che i byte oltre al minimo vengano resettati. Per questo c'è bitwise e ( 0xFF &). L'uso dei calchi sta offuscando l'intenzione.
Öö Tiib,

28

La domanda è più grande del semplice utilizzo di wither static_cast o casting in stile C perché ci sono diverse cose che accadono quando si usano i cast in stile C. Gli operatori di casting C ++ intendono rendere più esplicite queste operazioni.

In superficie, i cast di static_cast e C style sembrano la stessa cosa, ad esempio quando si lancia un valore su un altro:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Entrambi eseguono il cast del valore intero in un doppio. Tuttavia, quando si lavora con i puntatori, le cose si complicano. qualche esempio:

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

In questo esempio (1) forse OK perché l'oggetto indicato da A è in realtà un'istanza di B. Ma cosa succede se a quel punto nel codice non si sa a cosa effettivamente indica? (2) forse perfettamente legale (vuoi solo guardare un byte dell'intero), ma potrebbe anche essere un errore nel qual caso un errore sarebbe carino, come (3). Gli operatori di casting C ++ intendono esporre questi problemi nel codice fornendo errori di compilazione o di runtime quando possibile.

Quindi, per un rigoroso "casting di valore" puoi usare static_cast. Se si desidera eseguire il casting polimorfico di runtime dei puntatori, utilizzare dynamic_cast. Se vuoi davvero dimenticare i tipi, puoi usare reintrepret_cast. E per lanciare const dalla finestra c'è const_cast.

Rendono solo il codice più esplicito in modo che sembri che tu sappia cosa stavi facendo.


26

static_castsignifica che non puoi accidentalmente const_casto reinterpret_cast, il che è una buona cosa.


4
Ulteriori vantaggi (anche se piuttosto minori) rispetto al cast in stile C è che si distingue di più (fare qualcosa di potenzialmente cattivo dovrebbe apparire brutto) ed è più accessibile.
Michael Burr,

4
L'abilità grep è sempre un vantaggio, nel mio libro.
Branan,

7
  1. Permette di trovare facilmente i cast nel tuo codice usando grep o strumenti simili.
  2. Rende esplicito il tipo di cast che stai facendo e coinvolge l'aiuto del compilatore per applicarlo. Se vuoi solo eliminare la costanza, puoi usare const_cast, che non ti permetterà di fare altri tipi di conversioni.
  3. I cast sono intrinsecamente brutti: tu come programmatore stai annullando il modo in cui il compilatore tratterebbe normalmente il tuo codice. Stai dicendo al compilatore "lo so meglio di te". Stando così le cose, ha senso che eseguire un cast dovrebbe essere una cosa moderatamente dolorosa da fare e che dovrebbero sporgere nel tuo codice, poiché sono una probabile fonte di problemi.

Vedi Introduzione al C ++ efficace


Sono completamente d'accordo con questo per le lezioni, ma ha senso usare cast in stile C ++ per i tipi POD?
Zachary Kraus,

Credo di si. Tutti e 3 i motivi si applicano ai POD, ed è utile avere una sola regola, piuttosto che regole separate per classi e POD.
JohnMcG,

Interessante, potrei dover modificare il modo in cui eseguo i cast nel codice futuro per i tipi POD.
Zachary Kraus,

7

Si tratta di quanta sicurezza del tipo vuoi imporre.

Quando scrivi (bar) foo(il che equivale a reinterpret_cast<bar> foose non hai fornito un operatore di conversione del tipo) stai dicendo al compilatore di ignorare la sicurezza del tipo, e fai solo come è stato detto.

Quando scrivi static_cast<bar> foostai chiedendo al compilatore di controllare almeno che la conversione del tipo abbia senso e, per i tipi integrali, di inserire un codice di conversione.


MODIFICA 26/02/2014

Ho scritto questa risposta più di 5 anni fa e ho sbagliato. (Vedi commenti.) Ma ottiene ancora voti!


8
(bar) foo non equivale a reinterpret_cast <bar> (foo). Le regole per "(TYPE) expr" sono che sceglierà il cast appropriato di stile C ++ da usare, che può includere reinterpret_cast.
Richard Corden,

Buon punto. Euro Micelli ha dato la risposta definitiva a questa domanda.
Pitarou,

1
Inoltre, lo è static_cast<bar>(foo), tra parentesi. Lo stesso per reinterpret_cast<bar>(foo).
LF

6

I cast di stile C sono facili da perdere in un blocco di codice. I cast in stile C ++ non sono solo buone pratiche; offrono un grado di flessibilità molto maggiore.

reinterpret_cast consente integrali conversioni di tipo puntatore, tuttavia può essere pericoloso se utilizzato in modo improprio.

static_cast offre una buona conversione per tipi numerici, ad es. da enum a ints o ints a float o qualsiasi tipo di dato di cui si è certi del tipo. Non esegue alcun controllo del tempo di esecuzione.

dynamic_cast, d'altra parte, eseguirà questi controlli segnalando eventuali assegnazioni o conversioni ambigue. Funziona solo su puntatori e riferimenti e comporta un sovraccarico.

Ce ne sono un paio, ma questi sono i principali che incontrerai.


4

static_cast, oltre a manipolare i puntatori alle classi, può anche essere utilizzato per eseguire conversioni esplicitamente definite nelle classi, nonché per eseguire conversioni standard tra tipi fondamentali:

double d = 3.14159265;
int    i = static_cast<int>(d);

4
Perché qualcuno dovrebbe scrivere static_cast<int>(d), tuttavia, quando (int)dè molto più conciso e leggibile? (Intendo nel caso dei tipi base, non dei puntatori oggetto.)
Todd Lehman

@ gd1 - Perché qualcuno dovrebbe mettere la coerenza al di sopra della leggibilità? (in realtà mezzo serio)
Todd Lehman,

2
@ToddLehman: Io, considerando che fare un'eccezione per alcuni tipi solo perché sono in qualche modo speciali per te non ha alcun senso per me, e anche io non sono d'accordo sulla tua stessa nozione di leggibilità. Più breve non significa più leggibile, come vedo dall'immagine che hai pubblicato in un altro commento.
gd1,

2
static_cast è una decisione chiara e consapevole di effettuare un tipo molto particolare di conversione. Si aggiunge quindi alla chiarezza delle intenzioni. È anche molto utile come indicatore per cercare conversioni nei file sorgente in una revisione del codice, in un bug o in un esercizio di aggiornamento.
Persixty,

1
Contrappunto @ToddLehman: perché qualcuno dovrebbe scrivere (int)dquando int{d}è molto più leggibile? Funzione di costruzione o simil-funzione se presente (), la sintassi non è così veloce da trasformarsi in un labirinto da incubo di parentesi in espressioni complesse. In questo caso, sarebbe int i{d}invece di int i = (int)d. IMO molto migliore. Detto questo, quando ho solo bisogno di un temporaneo in un'espressione, uso static_caste non ho mai usato i cast del costruttore, non credo. Uso solo (C)castsquando scrivo in fretta il debug couts ...
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.