Perché si dovrebbe evitare il casting? [chiuso]


97

In genere evito il più possibile i tipi di casting poiché ho l'impressione che sia una cattiva pratica di codifica e potrebbe incorrere in una penalità nelle prestazioni.

Ma se qualcuno mi chiedesse di spiegare perché esattamente è così, probabilmente li guarderei come un cervo alla luce dei fari.

Allora perché / quando il casting è sbagliato?

È generale per java, c #, c ++ o ogni diverso ambiente di runtime se ne occupa alle proprie condizioni?

Le specifiche per qualsiasi lingua sono benvenute, ad esempio perché è cattivo in c ++?


3
Dove hai avuto questa impressione?
Oded

8
Ho avuto questa impressione dato che non ho mai letto un libro o incontrato un programmatore che ha detto "CASTING SOOOO GOOOOD !!!"
LoudNPossiblyWrong

15
La risposta per C ++ è inevitabilmente diversa dalla risposta per C #. Questo è molto specifico per la lingua. Le risposte fino ad ora rispondono a questa domanda per lingue specifiche e in alcuni casi non indicano di quale lingua stanno parlando.
James McNellis

2
Bad è un termine relativo. Evitare il casting è una best practice, ma a volte un programmatore deve fare ciò che deve fare un programmatore. (soprattutto se stai scrivendo un programma java 1.5+ che utilizza una libreria scritta per 1.4) Forse rinomina la domanda "Perché il casting dovrebbe essere evitato?"
Mike Miller

2
Questa è una grande domanda .... È stata chiusa da 5 utenti che hanno ciascuno meno di 10.000 reputazione !! Un uso troppo zelante di nuovi poteri, chiaramente. Hanno votato per riaprire.
reach4thelasers

Risposte:


141

Hai etichettato questo con tre lingue e le risposte sono davvero molto diverse tra le tre. La discussione del C ++ implica più o meno la discussione anche dei cast in C, e questo fornisce (più o meno) una quarta risposta.

Dato che è quello che non hai menzionato esplicitamente, inizierò con C. I cast hanno una serie di problemi. Uno è che possono fare una serie di cose diverse. In alcuni casi, il cast non fa altro che dire al compilatore (in sostanza): "taci, so cosa sto facendo" - cioè, assicura che anche quando fai una conversione che potrebbe causare problemi, il compilatore non ti avviserà di questi potenziali problemi. Solo per esempio char a=(char)123456;,. Il risultato esatto di questa implementazione definita (dipende dalla dimensione e dalla firma dichar), e tranne in situazioni piuttosto strane, probabilmente non è utile. I cast C variano anche a seconda che siano qualcosa che accade solo in fase di compilazione (cioè, stai solo dicendo al compilatore come interpretare / trattare alcuni dati) o qualcosa che accade in fase di esecuzione (ad esempio, una conversione effettiva da double a lungo).

Il C ++ tenta di risolvere questo problema almeno in una certa misura aggiungendo un numero di "nuovi" operatori di cast, ognuno dei quali è limitato solo a un sottoinsieme delle capacità di un cast in C. Ciò rende più difficile (ad esempio) eseguire accidentalmente una conversione che non intendevi davvero: se intendi solo eliminare la costanza su un oggetto, puoi usarlo const_casted essere sicuro che l' unica cosa che può influenzare è se un oggetto è const, volatileo non. Al contrario, a static_castnon è consentito influenzare se un oggetto è constovolatile. In breve, hai la maggior parte degli stessi tipi di funzionalità, ma sono classificati in modo che un cast possa generalmente eseguire solo un tipo di conversione, in cui un singolo cast in stile C può eseguire due o tre conversioni in un'unica operazione. L'eccezione principale è che puoi usare un dynamic_castal posto di static_casta almeno in alcuni casi e nonostante sia scritto come a dynamic_cast, finirà davvero come a static_cast. Ad esempio, puoi utilizzare dynamic_castper spostarti verso l'alto o verso il basso una gerarchia di classi, ma un cast "alto" della gerarchia è sempre sicuro, quindi può essere fatto staticamente, mentre un cast "basso" della gerarchia non è necessariamente sicuro, quindi è fatto dinamicamente.

Java e C # sono molto più simili tra loro. In particolare, con entrambi il casting è (praticamente?) Sempre un'operazione di runtime. In termini di operatori di cast C ++, di solito è il più vicino a un dynamic_castin termini di ciò che è realmente fatto - cioè, quando si tenta di eseguire il cast di un oggetto su un tipo di destinazione, il compilatore inserisce un controllo in fase di esecuzione per vedere se tale conversione è consentita e genera un'eccezione se non lo è. I dettagli esatti (ad esempio, il nome usato per l'eccezione "cast errato") variano, ma il principio di base rimane per lo più simile (sebbene, se la memoria serve, Java rende i cast applicati ai pochi tipi non oggetto come intmolto più vicini a C cast - ma questi tipi sono usati abbastanza raramente che 1) non me lo ricordo per certo, e 2) anche se è vero, non importa molto comunque).

Guardando le cose più in generale, la situazione è piuttosto semplice (almeno IMO): un cast (ovviamente abbastanza) significa che stai convertendo qualcosa da un tipo a un altro. Quando / se lo fai, solleva la domanda "Perché?" Se vuoi davvero che qualcosa sia di un tipo particolare, perché non l'hai definito come quel tipo con cui iniziare? Questo non vuol dire che non ci sia mai un motivo per eseguire una tale conversione, ma ogni volta che accade, dovrebbe sorgere la domanda se è possibile riprogettare il codice in modo da utilizzare il tipo corretto dappertutto. Anche conversioni apparentemente innocue (ad esempio, tra numero intero e virgola mobile) dovrebbero essere esaminate molto più da vicino di quanto sia comune. Nonostante il loro aspettosomiglianza, i numeri interi dovrebbero davvero essere usati per tipi di cose "contate" e virgola mobile per tipi di cose "misurate". Ignorare la distinzione è ciò che porta ad alcune affermazioni folli come "la famiglia americana media ha 1,8 figli". Anche se tutti possiamo vedere come ciò avvenga, il fatto è che nessuna famiglia ha 1,8 figli. Potrebbero averne 1 o 2 o potrebbero averne di più, ma mai 1.8.


10
Sembra che tu stia soffrendo "morte per capacità di attenzione" qui, questa è una bella risposta imo.
Steve Townsend

2
Una risposta forte, ma alcune delle parti "Non lo so" potrebbero essere ristrette per renderle complete. @ Dragontamer5788 è una buona risposta, ma non esaustiva.
M2tM

5
Un bambino intero e un bambino con una gamba mancante sarebbero 1,8 bambini. Ma una risposta molto bella nondimeno. :)
Oz.

1
Una risposta simpaticissima non ostacolata minimamente dall'esclusione delle coppie senza figli.

@ Roger: non escludere, solo ignorare.
Jerry Coffin

48

Molte buone risposte qui. Ecco come la vedo io (da una prospettiva C #).

Casting di solito significa una delle due cose:

  • Conosco il tipo di runtime di questa espressione ma il compilatore non lo conosce. Compilatore, ti dico, in fase di esecuzione l'oggetto che corrisponde a questa espressione sarà davvero di questo tipo. A partire da ora, sai che questa espressione deve essere trattata come se fosse di questo tipo. Genera codice che presume che l'oggetto sarà del tipo specificato o, se sbaglio, genera un'eccezione.

  • Sia il compilatore che lo sviluppatore conoscono il tipo di runtime dell'espressione. Esiste un altro valore di un tipo diverso associato al valore che questa espressione avrà in fase di esecuzione. Genera codice che produce il valore del tipo desiderato dal valore del tipo dato; se non puoi farlo, lancia un'eccezione.

Nota che quelli sono opposti . Esistono due tipi di calchi! Ci sono cast in cui stai dando un suggerimento al compilatore sulla realtà - ehi, questa cosa di tipo object è in realtà di tipo Customer - e ci sono cast in cui stai dicendo al compilatore di eseguire una mappatura da un tipo a un altro - hey, Mi serve l'int che corrisponde a questo doppio.

Entrambi i tipi di cast sono bandiere rosse. Il primo tipo di cast solleva la domanda "perché esattamente lo sviluppatore sa qualcosa che il compilatore non sa?" Se siete in questa situazione, allora la cosa migliore da fare è di solito di modificare il programma in modo che il compilatore non ha una maniglia sulla realtà. Allora non hai bisogno del cast; l'analisi viene eseguita in fase di compilazione.

Il secondo tipo di cast solleva la domanda "perché l'operazione non viene eseguita nel tipo di dati di destinazione in primo luogo?" Se hai bisogno di un risultato in int, perché tieni un doppio in primo luogo? Non dovresti tenere un int?

Alcuni pensieri aggiuntivi qui:

http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/


2
Non penso che siano intrinsecamente bandiere rosse. Se hai un Foooggetto che eredita da Bare lo memorizzi in a List<Bar>, allora avrai bisogno di cast se lo vuoi Fooindietro. Forse indica un problema a livello architettonico (perché memorizziamo Bars invece di Foos?), Ma non necessariamente. E, se Fooanche questo ha un cast valido int, si occupa anche dell'altro tuo commento: stai memorizzando un Foo, non un int, perché un intnon è sempre appropriato.
Mike Caron

6
@Mike Caron - Non posso rispondere per Eric, ovviamente, ma per me una bandiera rossa significa "questo è qualcosa a cui pensare", non "questo è qualcosa di sbagliato". E non ci sono problemi a memorizzare un fileFoo in a List<Bar>, ma al momento del cast stai cercando di fare qualcosa Fooche non è appropriato per a Bar. Ciò significa che il comportamento diverso per i sottotipi viene eseguito attraverso un meccanismo diverso dal polimorfismo integrato fornito dai metodi virtuali. Forse è la soluzione giusta, ma più spesso è una bandiera rossa.
Jeffrey L Whitledge

14
Infatti. Se stai estraendo delle cose da una lista di animali e in seguito devi dirlo al compilatore, oh, a proposito, so che la prima è una tigre, la seconda è un leone e la terza è un orso, quindi avresti dovuto usare una Tupla <Leone, Tigre, Orso>, non una Lista <Animale>.
Eric Lippert

5
Sono d'accordo con te, ma penso che senza un migliore supporto sintattico per le Tuple<X,Y,...>tuple è improbabile che vedrà un uso diffuso in C #. Questo è un posto in cui la lingua potrebbe fare un lavoro migliore per spingere le persone verso la "fossa del successo".
kvb

4
@kvb: sono d'accordo. Abbiamo considerato l'adozione di una sintassi della tupla per C # 4 ma non rientrava nel budget. Forse in C # 5; non abbiamo ancora elaborato il set completo di funzionalità. Troppo impegnato a raccogliere il CTP per l'asincronia. O forse in un'ipotetica versione futura.
Eric Lippert

36

Gli errori di trasmissione vengono sempre segnalati come errori di runtime in java. L'uso di generici o modelli trasforma questi errori in errori in fase di compilazione, rendendo molto più facile rilevare quando si è commesso un errore.

Come ho detto sopra. Questo non vuol dire che tutti i casting siano cattivi. Ma se è possibile evitarlo, è meglio farlo.


4
Ciò presuppone che tutto il casting sia "cattivo"; tuttavia, questo non è del tutto vero. Prendi C #, ad esempio, con supporto cast sia implicito che esplicito. Il problema nasce quando viene eseguito un cast che (accidentalmente) rimuove le informazioni o l'indipendenza dai tipi (questo varia in base alla lingua).

5
In realtà, questo è completamente sbagliato in C ++. Forse è necessaria una modifica per includere le lingue targetizzate con queste informazioni.
Steve Townsend

@Erick - Lo farei, ma non so la prima cosa su Java, né abbastanza dettagli C # per essere sicuro che le informazioni siano corrette.
Steve Townsend

Mi spiace, sono un tipo Java, quindi la mia conoscenza di c ++ è abbastanza limitata. Potrei modificare la parola "modello" fuori da esso, poiché doveva essere vago.
Mike Miller

Non così. Alcuni errori di cast vengono rilevati dal compilatore Java e non solo da quelli generici. Considera (String) 0;
Marchese di Lorne

18

Il casting non è intrinsecamente negativo, è solo che è spesso usato impropriamente come mezzo per ottenere qualcosa che in realtà non dovrebbe essere fatto affatto, o fatto in modo più elegante.

Se fosse universalmente cattivo, le lingue non lo supporterebbero. Come ogni altra caratteristica della lingua, ha il suo posto.

Il mio consiglio è di concentrarti sulla tua lingua principale e di comprenderne tutti i cast e le migliori pratiche associate. Ciò dovrebbe informare le escursioni in altre lingue.

I documenti C # rilevanti sono qui .

C'è un ottimo riepilogo sulle opzioni C ++ in una precedente domanda SO qui .


9

Parlo principalmente per C ++ qui, ma la maggior parte di questo probabilmente si applica anche a Java e C #:

C ++ è un linguaggio tipizzato staticamente . Ci sono alcuni margini di manovra che il linguaggio ti consente in questo (funzioni virtuali, conversioni implicite), ma fondamentalmente il compilatore conosce il tipo di ogni oggetto in fase di compilazione. Il motivo per utilizzare un tale linguaggio è che gli errori possono essere rilevati in fase di compilazione . Se il compilatore conosce i tipi di ae b, allora ti rileverà in fase di compilazione quando fai a=bdove aè un numero complesso ed bè una stringa.

Ogni volta che esegui un casting esplicito, dici al compilatore di stare zitto, perché pensi di sapere meglio . Nel caso in cui ti sbagli, di solito lo scoprirai solo in fase di esecuzione . E il problema con lo scoprire in fase di esecuzione è che questo potrebbe essere presso un cliente.


5

Java, c # e c ++ sono linguaggi fortemente tipizzati, sebbene i linguaggi fortemente tipizzati possano essere visti come poco flessibili, hanno il vantaggio di eseguire il controllo del tipo in fase di compilazione e ti proteggono dagli errori di runtime causati dal tipo sbagliato per determinate operazioni.

Esistono fondamentalmente due tipi di cast: un cast su un tipo più generale o un cast su un altro tipo (più specifico). Il cast su un tipo più generale (cast su un tipo genitore) lascerà intatti i controlli del tempo di compilazione. Ma il casting su altri tipi (tipi più specifici) disabiliterà il controllo del tipo in fase di compilazione e sarà sostituito dal compilatore da un controllo di runtime. Ciò significa che hai meno certezza che il codice compilato verrà eseguito correttamente. Ha anche un impatto trascurabile sulle prestazioni, a causa del controllo del tipo di runtime extra (l'API Java è piena di cast!).


5
Non tutti i cast "ignorano" l'indipendenza dai tipi in C ++.
James McNellis

4
Non tutti i cast "ignorano" l'indipendenza dai tipi in C #.

5
Non tutti i cast "ignorano" l'indipendenza dai tipi in Java.
Erick Robertson

11
Non tutti i cast "bypassano" l'indipendenza dal tipo in Casting. Oh aspetta, quel tag non si riferisce a una lingua ...
sbi

2
In quasi tutti i casi in C # e Java, il casting darà un degrado delle prestazioni, poiché il sistema eseguirà un controllo del tipo in fase di esecuzione (che non è gratuito). In C ++, dynamic_castè generalmente più lento di static_cast, poiché generalmente deve eseguire il controllo del tipo in fase di esecuzione (con alcuni avvertimenti: il casting su un tipo di base è economico, ecc.).
Travis Gockel

4

Alcuni tipi di casting sono così sicuri ed efficienti da non essere spesso nemmeno considerati casting.

Se si esegue il cast da un tipo derivato a un tipo di base, questo è generalmente abbastanza economico (spesso, a seconda della lingua, dell'implementazione e di altri fattori, è a costo zero) ed è sicuro.

Se lanci da un tipo semplice come un int a un tipo più ampio come un int lungo, di nuovo è spesso abbastanza economico (generalmente non molto più costoso dell'assegnazione dello stesso tipo di quel cast a) e di nuovo è sicuro.

Altri tipi sono più pesanti e / o più costosi. Nella maggior parte dei linguaggi il casting da un tipo di base a un tipo derivato è economico ma ha un alto rischio di errori gravi (in C ++ se si esegue static_cast da base a derivato sarà economico, ma se il valore sottostante non è del tipo derivato il il comportamento è indefinito e può essere molto strano) o relativamente costoso e con il rischio di sollevare un'eccezione (dynamic_cast in C ++, cast esplicito da base a derivato in C # e così via). Il pugilato in Java e C # è un altro esempio di questo e una spesa ancora maggiore (considerando che stanno cambiando più del modo in cui vengono trattati i valori sottostanti).

Altri tipi di cast possono perdere informazioni (un tipo intero lungo in un tipo intero breve).

Questi casi di rischio (sia di eccezione che di errore più grave) e di spesa sono tutti motivi per evitare il casting.

Un motivo più concettuale, ma forse più importante, è che ogni caso di casting è un caso in cui la tua capacità di ragionare sulla correttezza del tuo codice è ostacolata: ogni caso è un altro posto in cui qualcosa può andare storto e il modo in cui può andare storto aumentare la complessità di dedurre se il sistema nel suo complesso andrà storto. Anche se il cast è dimostrabilmente al sicuro ogni volta, provare che questa è una parte extra del ragionamento.

Infine, l'uso pesante di cast può indicare una mancata considerazione del modello a oggetti nel crearlo, usarlo o entrambi: il casting avanti e indietro tra gli stessi pochi tipi frequentemente è quasi sempre un fallimento nel considerare le relazioni tra i tipi Usato. Qui non è tanto che i calchi siano cattivi, quanto sono un segno di qualcosa di brutto.


2

C'è una tendenza crescente da parte dei programmatori ad aggrapparsi a regole dogmatiche sull'uso delle caratteristiche del linguaggio ("non usare mai XXX!", "XXX considerato dannoso", ecc.), Dove XXX varia da gotosa puntatori a protectedmembri di dati a singleton per passare oggetti da valore.

Seguire tali regole, nella mia esperienza, garantisce due cose: non sarai un pessimo programmatore, né sarai un grande programmatore.

Un approccio molto migliore è scavare e scoprire il nocciolo di verità dietro questi divieti generali , e quindi utilizzare le funzionalità con giudizio, con la consapevolezza che ci sono molte situazioni per le quali sono lo strumento migliore per il lavoro.

"In genere evito di lanciare i tipi il più possibile" è un buon esempio di una regola così generalizzata. I calchi sono essenziali in molte situazioni comuni. Qualche esempio:

  1. Quando si interagisce con codice di terze parti (specialmente quando quel codice è pieno di typedefs). (Esempio: GLfloat<--> double<-->Real .)
  2. Casting da un puntatore / riferimento alla classe base derivata: questo è così comune e naturale che il compilatore lo farà implicitamente. Se renderlo esplicito aumenta la leggibilità, il cast è un passo avanti, non indietro!
  3. Casting da un puntatore / riferimento a una classe derivata: anche comune, anche in codice ben progettato. (Esempio: contenitori eterogenei.)
  4. Serializzazione / deserializzazione binaria interna o altro codice di basso livello che necessita di accesso ai byte grezzi dei tipi incorporati.
  5. Ogni volta che è semplicemente più naturale, conveniente e leggibile utilizzare un tipo diverso. (Esempio: std::size_type-> int.)

Ci sono certamente molte situazioni in cui non è appropriato usare un cast, ed è importante imparare anche queste; Non entrerò troppo nei dettagli poiché le risposte sopra hanno fatto un buon lavoro evidenziandone alcune.


1

Per approfondire la risposta di KDeveloper , non è intrinsecamente indipendente dai tipi. Con il casting, non vi è alcuna garanzia che ciò da cui stai trasmettendo e a cui stai trasmettendo corrisponderà e, in tal caso, otterrai un'eccezione di runtime, che è sempre una cosa negativa.

Per quanto riguarda in particolare il C #, poiché include gli operatori ise as, hai l'opportunità (per la maggior parte) di stabilire se un cast avrà successo o meno. Per questo motivo, è necessario eseguire i passaggi appropriati per determinare se l'operazione riuscirà o meno e procederà in modo appropriato.


1

In caso di C #, è necessario essere più attenti durante il casting a causa dei sovraccarichi di boxing / unboxing coinvolti durante la gestione dei tipi di valore.


1

Non sono sicuro che qualcuno lo abbia già menzionato, ma in C # il casting può essere usato in modo piuttosto sicuro ed è spesso necessario. Supponi di ricevere un oggetto che può essere di diversi tipi. Usando la isparola chiave puoi prima confermare che l'oggetto è effettivamente del tipo a cui stai per eseguirne il cast, quindi eseguire il cast dell'oggetto direttamente su quel tipo. (Non ho lavorato molto con Java, ma sono sicuro che anche lì c'è un modo molto semplice per farlo).


1

Lanci un oggetto solo su qualche tipo, se sono soddisfatte 2 condizioni:

  1. sai che è di quel tipo
  2. il compilatore no

Ciò significa che non tutte le informazioni che hai sono ben rappresentate nella struttura del tipo che utilizzi. Questo è un male, perché la tua implementazione dovrebbe semanticamente comprendere tuo modello, cosa che chiaramente non è in questo caso.

Ora, quando esegui un cast, questo può avere 2 diversi motivi:

  1. Hai fatto un pessimo lavoro nell'esprimere le relazioni tipo.
  2. il sistema di tipi delle lingue semplicemente non è abbastanza espressivo per esprimerle.

Nella maggior parte delle lingue ti imbatti nella seconda situazione molte volte. I generici come in Java aiutano un po ', il sistema di template C ++ ancora di più, ma è difficile da padroneggiare e anche allora alcune cose potrebbero essere impossibili o semplicemente non valerne la pena.

Quindi potresti dire che un cast è un trucco sporco per aggirare i tuoi problemi per esprimere una relazione di tipo specifica in una lingua specifica. Gli hack sporchi dovrebbero essere evitati. Ma non puoi mai vivere senza di loro.


0

In genere i modelli (o generici) sono più indipendenti dai tipi di cast. A questo proposito, direi che un problema con il casting è la sicurezza dei tipi. Tuttavia, c'è un altro problema più sottile associato soprattutto al downcasting: il design. Almeno dal mio punto di vista, il downcasting è un odore di codice, un'indicazione che qualcosa potrebbe essere sbagliato nel mio design e dovrei indagare ulteriormente. Perché è semplice: se "ottieni" correttamente le astrazioni, semplicemente non ne hai bisogno! Bella domanda a proposito ...

Saluti!


0

Per essere davvero concisi, una buona ragione è la portabilità. Un'architettura diversa che supporta entrambi la stessa lingua potrebbe avere, ad esempio, int di dimensioni diverse. Quindi, se eseguo la migrazione da ArchA a ArchB, che ha un int più ristretto, potrei vedere un comportamento strano nella migliore delle ipotesi e nel peggiore dei casi un errore.

(Sto chiaramente ignorando il bytecode indipendente dall'architettura e IL.)

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.