Cast normale vs static_cast vs. dynamic_cast

Scrivo codice C e C ++ da quasi vent’anni, ma c’è un aspetto di questi linguaggi che non ho mai veramente compreso. Ovviamente ho usato cast regolari, ad es

MyClass *m = (MyClass *)ptr; 

dappertutto, ma sembrano esserci altri due tipi di calchi, e non conosco la differenza. Qual è la differenza tra le seguenti righe di codice?

 MyClass *m = (MyClass *)ptr; MyClass *m = static_cast(ptr); MyClass *m = dynamic_cast(ptr); 

static_cast

static_cast è usato per i casi in cui fondamentalmente si vuole invertire una conversione implicita, con alcune restrizioni e aggiunte. static_cast non esegue controlli di runtime. Questo dovrebbe essere usato se sai che ti riferisci ad un object di un tipo specifico, e quindi un controllo non sarebbe necessario. Esempio:

 void func(void *data) { // Conversion from MyClass* -> void* is implicit MyClass *c = static_cast(data); ... } int main() { MyClass c; start_thread(&func, &c) // func(&c) will be called .join(); } 

In questo esempio, sapete di aver passato un object MyClass e quindi non vi è alcuna necessità di un controllo runtime per garantirlo.

dynamic_cast

dynamic_cast è utile quando non si conosce il tipo dinamico dell’object. Restituisce un puntatore nullo se l’object a cui si fa riferimento non contiene il tipo casted come class base (quando lanci su un riferimento, in questo caso viene lanciata un’eccezione bad_cast ).

 if (JumpStm *j = dynamic_cast(&stm)) { ... } else if (ExprStm *e = dynamic_cast(&stm)) { ... } 

Non è ansible utilizzare dynamic_cast se downcast ( dynamic_cast cast su una class derivata) e il tipo di argomento non è polimorfico. Ad esempio, il seguente codice non è valido, perché Base non contiene alcuna funzione virtuale:

 struct Base { }; struct Derived : Base { }; int main() { Derived d; Base *b = &d; dynamic_cast(b); // Invalid } 

Un “up-cast” (cast alla class base) è sempre valido sia con static_cast che dynamic_cast , e anche senza cast, poiché una “up-cast” è una conversione implicita.

Cast normale

Questi cast sono anche chiamati cast in stile C. Un cast in stile C è fondamentalmente identico a provare una serie di sequenze di cast di C ++ e prendere il primo cast di C ++ che funziona, senza mai considerare dynamic_cast . Inutile dire che questo è molto più potente in quanto combina tutti i const_cast , static_cast e reinterpret_cast , ma non è sicuro, perché non utilizza dynamic_cast .

Inoltre, i cast in stile C non solo ti permettono di farlo, ma ti permettono anche di lanciare in sicurezza su una class base privata, mentre la sequenza ” static_cast ” di “equivalenti” ti darebbe un errore in fase di compilazione.

Alcune persone preferiscono i cast in stile C a causa della loro brevità. Li uso solo per i cast numerici e uso i cast di C ++ appropriati quando sono coinvolti tipi definiti dall’utente, poiché forniscono un controllo più severo.

Cast statico

Il cast statico esegue conversioni tra tipi compatibili. È simile al cast in stile C, ma è più restrittivo. Ad esempio, il cast in stile C consente 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 causerà un errore di runtime o sovrascriverà la memoria adiacente.

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

In contrasto con il cast in stile C, il cast statico consentirà al compilatore di verificare che i tipi di dati di puntatore e punta siano compatibili, il che consente al programmatore di cogliere questa errata assegnazione del puntatore durante la compilazione.

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

Reinterpretare il cast

Per forzare la conversione del puntatore, nello stesso modo in cui viene eseguito il cast in stile C sullo sfondo, verrà utilizzata la cast di reinterpretazione.

 int *r = reinterpret_cast(&c); // forced conversion 

Questo cast gestisce le conversioni tra determinati tipi non correlati, ad esempio da un tipo di puntatore a un altro tipo di puntatore incompatibile. Semplicemente esegue una copia binaria dei dati senza alterare il pattern di bit sottostante. Si noti che il risultato di un’operazione di basso livello è specifico del sistema e quindi non portatile. Dovrebbe essere usato con caucanvas se non può essere evitato del tutto.

Cast dinamico

Questo è usato solo per convertire puntatori di oggetti e riferimenti a oggetti in altri tipi di puntatori o di riferimenti nella gerarchia di ereditarietà. È l’unico cast che si assicura che l’object puntato possa essere convertito, eseguendo un controllo run-time per verificare che il puntatore si riferisca a un object completo del tipo di destinazione. Affinché questo controllo run-time sia ansible, l’object deve essere polimorfico. Cioè, la class deve definire o ereditare almeno una funzione virtuale. Questo perché il compilatore genererà solo le informazioni sul tipo di runtime necessarie per tali oggetti.

Esempi di cast dinamici

Nell’esempio seguente, un puntatore MyChild viene convertito in un puntatore MyBase utilizzando un cast dinamico. Questa conversione derivata-base ha esito positivo, poiché l’object Bambino include un object Base completo.

 class MyBase { public: virtual void test() {} }; class MyChild : public MyBase {}; int main() { MyChild *child = new MyChild(); MyBase *base = dynamic_cast(child); // ok } 

L’esempio successivo tenta di convertire un puntatore MyBase in un puntatore MyChild. Poiché l’object Base non contiene un object Child completo, la conversione del puntatore fallirà. Per indicare ciò, il cast dinamico restituisce un puntatore nullo. Ciò offre un modo conveniente per verificare se una conversione è riuscita o meno durante l’esecuzione.

 MyBase *base = new MyBase(); MyChild *child = dynamic_cast(base); if (child == 0) std::cout << "Null pointer returned"; 

Se un riferimento viene convertito al posto di un puntatore, il cast dinamico fallirà lanciando un'eccezione bad_cast. Questo deve essere gestito utilizzando una dichiarazione try-catch.

 #include  // … try { MyChild &child = dynamic_cast(*base); } catch(std::bad_cast &e) { std::cout << e.what(); // bad dynamic_cast } 

Cast dinamico o statico

Il vantaggio dell'uso di un cast dinamico è che consente al programmatore di verificare se una conversione è riuscita o meno durante l'esecuzione. Lo svantaggio è che c'è un overhead delle prestazioni associato a questo controllo. Per questo motivo, l'utilizzo di un cast statico sarebbe stato preferibile nel primo esempio, poiché una conversione derivata-base non fallirebbe mai.

 MyBase *base = static_cast(child); // ok 

Tuttavia, nel secondo esempio la conversione può avere esito positivo o negativo. Fallirà se l'object MyBase contiene un'istanza MyBase e avrà successo se contiene un'istanza MyChild. In alcune situazioni questo potrebbe non essere noto fino al momento dell'esecuzione. Quando questo è il caso, il cast dinamico è una scelta migliore rispetto al cast statico.

 // Succeeds for a MyChild object MyChild *child = dynamic_cast(base); 

Se la conversione da base a derivata fosse stata eseguita utilizzando un cast statico anziché un cast dinamico, la conversione non avrebbe avuto esito negativo. Avrebbe restituito un puntatore che si riferiva a un object incompleto. Il dereferenziamento di un tale puntatore può portare a errori di runtime.

 // Allowed, but invalid MyChild *child = static_cast(base); // Incomplete MyChild object dereferenced (*child); 

Cast di Const

Questo è principalmente usato per aggiungere o rimuovere il modificatore const di una variabile.

 const int myConst = 5; int *nonConst = const_cast(&myConst); // removes const 

Sebbene la costante const permetta di modificare il valore di una costante, è comunque un codice non valido che può causare un errore di runtime. Ciò potrebbe verificarsi ad esempio se la costante si trovava in una sezione di memoria di sola lettura.

 *nonConst = 10; // potential run-time error 

Il cast di Const viene invece usato principalmente quando c'è una funzione che accetta un argomento puntatore non costante, anche se non modifica il pointee.

 void print(int *p) { std::cout << *p; } 

La funzione può quindi essere passata a una variabile costante utilizzando un cast const.

 print(&myConst); // error: cannot convert // const int* to int* print(nonConst); // allowed 

Fonte e altre spiegazioni

Si dovrebbe guardare l’articolo Programmazione C ++ / Type Casting .

Contiene una buona descrizione di tutti i diversi tipi di cast. Quanto segue tratto dal link precedente:

const_cast

const_cast (espressione) const_cast <> () è usato per aggiungere / rimuovere const (ness) (o volatile-ness) di una variabile.

static_cast

static_cast (espressione) Il parametro static_cast <> () viene utilizzato per eseguire il cast tra i tipi di interi. ‘eg’ char-> long, int-> short ecc.

Il cast statico è anche usato per lanciare puntatori a tipi correlati, ad esempio cast di casting * al tipo appropriato.

dynamic_cast

Il cast dinamico viene utilizzato per convertire puntatori e riferimenti in fase di esecuzione, generalmente allo scopo di trasmettere un puntatore o un riferimento su o giù per una catena di ereditarietà (gerarchia di ereditarietà).

dynamic_cast (espressione)

Il tipo di destinazione deve essere un puntatore o un tipo di riferimento e l’espressione deve valutare un puntatore o un riferimento. Il cast dinamico funziona solo quando il tipo di object a cui fa riferimento l’espressione è compatibile con il tipo di destinazione e la class base ha almeno una funzione membro virtuale. In caso contrario, e il tipo di espressione che si esegue il cast è un puntatore, viene restituito NULL, se un cast dinamico su un riferimento fallisce, viene generata un’eccezione bad_cast. Quando non fallisce, il cast dinamico restituisce un puntatore o un riferimento del tipo di destinazione all’object a cui si riferisce l’espressione.

reinterpret_cast

Reinterpretazione del cast, semplicemente, converte un tipo bit per bit in un altro. Qualsiasi puntatore o tipo integrale può essere castato a qualsiasi altro con reinterpretazione del cast, consentendo facilmente un uso improprio. Ad esempio, con il cast reinterpret si potrebbe, senza ragione, eseguire il cast di un puntatore intero su un puntatore a stringa.

Evitare l’uso di cast di tipo C.

I cast in stile C sono un misto di cast costitutivo e reinterpretato, ed è difficile trovare e sostituire il codice. Un programmatore di applicazioni C ++ dovrebbe evitare il cast di tipo C.

Per tua informazione, credo che Bjarne Stroustrup abbia affermato che i cast di stile C devono essere evitati e che dovresti usare static_cast o dynamic_cast se ansible.

Domande frequenti in stile C ++ di Barne Stroustrup

Prendi questo consiglio per quello che vuoi. Sono lontano dall’essere un guru del C ++.

I cast in stile C conflate const_cast, static_cast e reinterpret_cast.

Vorrei che il C ++ non avesse cast in stile C. I cast di C ++ si distinguono correttamente (come dovrebbero, i cast sono normalmente indicativi di fare qualcosa di male) e distinguono correttamente tra i diversi tipi di conversioni che eseguono. Permettono anche di scrivere funzioni simili, come ad esempio boost :: lexical_cast, che è abbastanza bello da una prospettiva di coerenza.

dynamic_cast ha il controllo del tipo di runtime e funziona solo con riferimenti e puntatori, mentre static_cast non offre il controllo del tipo di runtime. Per informazioni complete, consultare l’articolo MSDN static_cast Operator .

dynamic_cast supporta solo i tipi di puntatore e riferimento. Restituisce NULL se il cast è imansible se il tipo è un puntatore o genera un’eccezione se il tipo è un tipo di riferimento. Quindi, dynamic_cast può essere usato per verificare se un object è di un certo tipo, static_cast non può (si finirà semplicemente con un valore non valido).

I cast di tipo C (e altri) sono stati trattati nelle altre risposte.