c++ ita - Quando devono essere usati static_cast, dynamic_cast, const_cast e reinterpret_cast?




casting (7)

static_cast è il primo cast che dovresti provare a usare. Fa cose come conversioni implicite tra tipi (come int a float , o puntatore a void* ), e può anche chiamare funzioni di conversione esplicite (o implicite). In molti casi, l'affermazione esplicita di static_cast non è necessaria, ma è importante notare che la sintassi T(something) è equivalente a (T)something e dovrebbe essere evitata (ne parleremo più avanti). Una T(something, something_else) è sicura, tuttavia, e garantisce di chiamare il costruttore.

static_cast può anche eseguire il cast attraverso le gerarchie di ereditarietà. Non è necessario quando si lancia verso l'alto (verso una classe base), ma quando si lancia verso il basso può essere usato fino a quando non esegue il cast dell'ereditarietà virtual . Non esegue il controllo, tuttavia, ed è un comportamento indefinito a static_cast in una gerarchia verso un tipo che non è in realtà il tipo di oggetto.

const_cast può essere usato per rimuovere o aggiungere const a una variabile; nessun altro cast di C ++ è in grado di rimuoverlo (nemmeno reinterpret_cast ). È importante notare che la modifica di un valore precedentemente const è solo indefinita se la variabile originale è const ; se lo usi per togliere a const un riferimento a qualcosa che non è stato dichiarato con const , è sicuro. Questo può essere utile quando si sovraccaricano le funzioni dei membri basate su const , per esempio. Può anche essere utilizzato per aggiungere const a un oggetto, ad esempio per richiamare un sovraccarico della funzione membro.

const_cast funziona anche in modo volatile , anche se è meno comune.

dynamic_cast è utilizzato quasi esclusivamente per la gestione del polimorfismo. Puoi lanciare un puntatore o riferimento a qualsiasi tipo polimorfico a qualsiasi altro tipo di classe (un tipo polimorfico ha almeno una funzione virtuale, dichiarata o ereditata). Puoi usarlo per più di un semplice lancio verso il basso - puoi lanciare lateralmente o addirittura su un'altra catena. Il dynamic_cast cercherà l'oggetto desiderato e lo restituirà se possibile. Se non può, restituirà nullptr nel caso di un puntatore, o lancia std::bad_cast nel caso di un riferimento.

dynamic_cast ha alcune limitazioni, però. Non funziona se ci sono più oggetti dello stesso tipo nella gerarchia dell'ereditarietà (il cosiddetto 'diamante temuto') e non si utilizza virtual ereditarietà virtual . Può anche passare attraverso l'eredità pubblica - non riuscirà mai a viaggiare attraverso private ereditarietà protected o private . Questo è raramente un problema, tuttavia, poiché tali forme di ereditarietà sono rare.

reinterpret_cast è il cast più pericoloso e dovrebbe essere usato con parsimonia. Trasforma un tipo direttamente in un altro - come il cast del valore da un puntatore a un altro, o la memorizzazione di un puntatore in un int , o tutti i tipi di altre cose cattive. In gran parte, l'unica garanzia che si ottiene con reinterpret_cast è che normalmente se si restituisce il risultato al tipo originale, si otterrà lo stesso valore esatto (ma non se il tipo intermedio è inferiore al tipo originale). Ci sono un numero di conversioni che reinterpret_cast non può fare anche tu. Viene utilizzato principalmente per conversioni e manipolazioni bit particolarmente strane, come trasformare un flusso di dati non elaborati in dati reali o archiviare dati nei bit bassi di un puntatore allineato.

Il cast in stile C e il cast in stile funzione sono i cast che usano (type)object o type(object) , rispettivamente. Un cast in stile C è definito come il primo dei seguenti che ha esito positivo:

  • const_cast
  • static_cast (anche se ignora le restrizioni di accesso)
  • static_cast (vedi sopra), quindi const_cast
  • reinterpret_cast
  • reinterpret_cast , quindi const_cast

Può quindi essere usato come sostituto di altri cast in alcuni casi, ma può essere estremamente pericoloso a causa della capacità di deviare in un reinterpret_cast , e quest'ultimo dovrebbe essere preferito quando è necessaria la trasmissione esplicita, a meno che non si sia certi che static_cast avrà successo o reinterpret_cast fallirà. Anche allora, considera l'opzione più lunga e più esplicita.

I cast in stile C ignorano anche il controllo degli accessi quando si esegue un static_cast , il che significa che hanno la capacità di eseguire un'operazione che nessun altro cast può eseguire. Questo è principalmente un kludge, però, e nella mia mente è solo un'altra ragione per evitare i cast in stile C.

Quali sono gli usi corretti di:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value cast (type)value stile C.
  • type(value) getto in stile funzione type(value)

Come si decide quale utilizzare in quali casi specifici?


Mentre altre risposte descrivono bene tutte le differenze tra i cast di C ++, vorrei aggiungere una breve nota perché non dovresti usare cast di (Type) var C (Type) var e Type(var) .

Per i principianti C ++ i cast in stile C sembrano essere l'operazione di superset su cast di C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) e qualcuno potrebbe preferirli rispetto ai cast di C ++ . In effetti, il cast in stile C è il superset e più breve da scrivere.

Il problema principale dei cast in stile C è che nascondono l'intenzione reale del cast di sviluppatore. I cast in stile C possono fare praticamente tutti i tipi di casting da cast normalmente eseguiti da static_cast <> () e dynamic_cast <> () a cast potenzialmente pericolosi come const_cast <> (), dove il modificatore di cost può essere rimosso così le variabili const può essere modificato e reinterpret_cast <> () che può anche reinterpretare i valori interi ai puntatori.

Ecco il campione.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

Il motivo principale per cui i cast di C ++ sono stati aggiunti al linguaggio è stato quello di consentire a uno sviluppatore di chiarire le sue intenzioni - perché ha intenzione di fare quel cast. Usando i cast in stile C che sono perfettamente validi in C ++ rendi il tuo codice meno leggibile e più soggetto a errori soprattutto per altri sviluppatori che non hanno creato il tuo codice. Quindi per rendere il tuo codice più leggibile ed esplicito dovresti sempre preferire i cast di C ++ rispetto ai cast di tipo C.

Ecco una breve citazione dal libro di Bjarne Stroustrup (l'autore di C ++) The C ++ Programming Language 4th edition - pagina 302.

Questo cast in stile C è molto più pericoloso degli operatori di conversione denominati perché la notazione è più difficile da individuare in un programma di grandi dimensioni e il tipo di conversione previsto dal programmatore non è esplicito.


Utilizza dynamic_cast per convertire puntatori / riferimenti all'interno di una gerarchia di ereditarietà.

Usa static_cast per le conversioni di tipo ordinario.

Usa reinterpret_cast per la reinterpret_cast a basso livello dei pattern di bit. Usare con estrema cautela.

Usa const_cast per lanciare via const/volatile . Evita questo se non sei bloccato usando un'API const-errata.


(Molte delle spiegazioni teoriche e concettuali sono state fornite in precedenza)

Di seguito sono riportati alcuni esempi pratici in cui ho utilizzato static_cast , dynamic_cast , const_cast , reinterpret_cast .

(Si riferisce anche a questo per capire la spiegazione: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

dynamic_cast:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}

Questo risponde alla tua domanda?

Non ho mai usato reinterpret_cast e mi chiedo se imbattersi in un caso che ne ha bisogno non è un odore di cattivo design. Nel codice base che lavoro su dynamic_cast è usato molto. La differenza con static_cast è che un dynamic_cast esegue il controllo runtime che può (più sicuro) o meno (più overhead) essere quello che vuoi (vedi msdn ).


Oltre alle altre risposte finora, ecco un esempio non ovvio in cui static_cast non è sufficiente, quindi è necessario reinterpret_cast . Supponiamo che esista una funzione che in un parametro di output restituisce puntatori a oggetti di classi diverse (che non condividono una classe base comune). Un vero esempio di tale funzione è CoCreateInstance() (vedere l'ultimo parametro, che in realtà è void** ). Si supponga di richiedere una particolare classe di oggetto da questa funzione, in modo da conoscere in anticipo il tipo per il puntatore (che si fa spesso per gli oggetti COM). In questo caso non puoi lanciare il puntatore al tuo puntatore nel void** con static_cast : hai bisogno di reinterpret_cast<void**>(&yourPointer) .

Nel codice:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

Tuttavia, static_cast funziona per semplici puntatori (non puntatori a puntatori), quindi il codice sopra può essere riscritto per evitare reinterpret_cast (al prezzo di una variabile extra) nel modo seguente:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

Un puntatore intelligente è una classe, un wrapper di un puntatore normale. A differenza dei normali puntatori, il ciclo di vita di smart point si basa su un conteggio di riferimenti (quante volte viene assegnato l'oggetto puntatore intelligente). Quindi, ogni volta che un puntatore intelligente viene assegnato a un altro, il numero di riferimento interno più il segno più. E ogni volta che l'oggetto esce dal campo di applicazione, il numero di riferimento viene meno meno.

Il puntatore automatico, anche se sembra simile, è totalmente diverso dal puntatore intelligente. È una classe conveniente che rilascia la risorsa ogni volta che un oggetto puntatore automatico esce dall'ambito della variabile. In una certa misura, rende un puntatore (alla memoria allocata dinamicamente) funziona in modo simile a una variabile stack (allocata staticamente in fase di compilazione).







c++ pointers casting c++-faq