2013-04-16 18 views
6

Devo prestare attenzione al modificatore const che funziona con i tipi primitivi? Quale è più sintatticamente corretto e perché?Modificatore const C++ con tipi primitivi

Prima versione:

float Foo::bar(float a, float b) 
{ 
    return (a + b)/2.0f; 
} 

Seconda versione:

const float Foo::bar(const float a, const float b) 
{ 
    return (a + b)/2.0f; 
} 

terza versione:

float Foo::bar(const float a, const float b) 
{ 
    return (a + b)/2.0f; 
} 

so le variabili primitive tipizzato vengono copiati caso di passaggio da un metodo, ma quale via è più chiara?

risposta

13

Direi che la terza versione è la più "corretta".

Si dice al compilatore che gli argomenti sono const, che è corretto poiché non li si modifica. Questo può aiutare il compilatore con ottimizzazioni per il passaggio degli argomenti, oltre che nei calcoli.

E il tipo di reso non è const poiché il chiamante potrebbe voler modificare il valore restituito. Se il chiamante non desidera modificare il valore restituito, spetta al chiamante assegnarlo a una variabile const.

Avrei anche aggiunto const alla dichiarazione di funzione, dato che la funzione non modifica nulla nell'oggetto:

float Foo::bar(const float a, const float b) const 
+1

+1 funzione membro const (anche se un'aggiunta utile a ciò che l'OP chiedeva). – wmorrison365

+2

"il chiamante potrebbe voler modificare il valore restituito" - anche se avranno bisogno di un 'const_cast' (o cast in stile C) per farlo, dal momento che si tratta di un'espressione rvalore di tipo primitivo. Quindi, anche se non è const, non possono ottenere un riferimento non const con esso senza cast. Non è sicuro che sia qualcosa che * dovremmo * supportare *, ma soprattutto se la funzione restituisce un oggetto 'const' di tipo di classe che in genere non funziona, perché impedisce di spostarsi da esso o chiamare funzioni di membri non-const di esso. –

+0

Un ulteriore dettaglio, se il tipo di ritorno era 'bool', è buono renderlo' const' per evitare l'assegnazione accidentale nelle espressioni condizionali. Questo può essere evitato alzando gli avvisi del compilatore, ma, una volta morso due volte timido. –

0

Il modo migliore è questo quanto ne sappia:

const float Foo::bar(const float& a, const float& b) const 
{ 
    return (a + b)/2.0f; 
} 

Questo informa il co mpiler che Notthing cambierà, e anche senza bisogno di copiare il valore dei parametri.

+4

Perché è meglio degli altri ?? Se passi di valore, non modifichi anche i dati originali.Se si passa il riferimento - nessuna copia del valore, ma c'è una copia del riferimento stesso – nogard

+0

Ciò può anche produrre un sovraccarico dovuto a causa di indiretta (se non è ottimizzato) come riferimento è fondamentalmente un puntatore dietro le quinte. –

1

Non c'è alcuna differenza tra il 2 ° e il 3 ° versioni. Scegli uno che è il più breve da digitare :)

C'è una piccola differenza tra il 1 ° e il 3 °. Potresti preferire il 3 ° se hai paura di modificare accidentalmente a e b all'interno della funzione.

+0

C'è comunque una differenza tra la 2a e 3a risposta. Il 2 ° restituisce un 'const float' che significa che il valore che restituisce non può essere modificato. nella terza puoi, ovviamente. – JasoonS

0

Risposta breve: non importa.

Risposta lunga: poiché si passano i due argomenti in base al valore e si restituisce l'argomento in base al valore. O uno di quelli va bene, ma vedrai più comunemente la prima versione.

Se si passa per riferimento (come altri hanno suggerito), allora è importante e si dovrebbe usare un riferimento const. Tuttavia, il passaggio di tipi primitivi per riferimento non offre realmente alcun vantaggio o senso (se si tratta di un riferimento const). La ragione di ciò è dovuta al fatto che il passaggio di tipi primitivi per valore non produrrà alcun overhead rispetto al passaggio primitivo per riferimento.

1

Ho intenzione di dire che con i primitivi potrebbe essere più efficiente copiarli effettivamente. Quando si passa un riferimento, il compilatore deve ancora passare dei byte sullo stack e quindi deve dereferenziare l'indirizzo per ottenere il contenuto.

Inoltre, il passaggio di valore supera qualsiasi possibile problema di concorrenza/volatilità relativo alla memoria di ciò che viene passato.

È un caso di "non cercare di ottimizzare qui".

Il ritorno da const è stile. Io di solito no, altri preferiscono nel caso in cui qualcuno sia disposto a fare qualcosa con il valore restituito. In seguito troverai persone che le restituiscono per riferimento al valore r ...

Normalmente sceglierei la prima opzione. L'altra alternativa è passare per valore (non è necessario utilizzare const) e restituire per valore const.

+0

"Avanti troverete le persone che le restituiscono con riferimento al valore r" - che provocherebbe UB, dal momento che non è possibile restituire un riferimento rvalue a un oggetto locale più di quanto sia possibile restituire un riferimento semplice a un oggetto locale. Speriamo che queste persone possano essere curate abbastanza velocemente. –

2

Prima di tutto, tutte le definizioni fornite sono corrette in modo sintetico. Se si compilano, sono corretti in termini di sintassi.

Il qualificatore const sui parametri ha un solo scopo: impedire al corpo della funzione di modificare gli argomenti qualificati const.

Nel caso specifico del codice di esempio, il metodo Foo::bar non modifica gli argomenti, pertanto l'uso del qualificatore const non ha alcun effetto.

Tuttavia, è possibile utilizzare const per impostazione predefinita in tutti i casi e rimuoverlo solo per le situazioni in cui si desidera consentire le modifiche. Quindi, applicarlo ai parametri di Foo::bar è una buona idea. Penso che sia una buona pratica, anche se devo ammettere che raramente la uso, a causa del rumore che comporta, il che potrebbe ridurre la leggibilità.

Un'altra cosa da considerare è che per i tipi primitivi, o più precisamente i tipi che non sono puntatori o non contengono puntatori, la modifica di un argomento passato per valore (cioè non per riferimento) non avrà alcun effetto collaterale: parametri di questi tipi agiscono realmente come variabili locali inizializzate (che possono essere utili, ma possono anche essere confuse). Per i puntatori, qualsiasi modifica dei dati puntati colerà nel mondo esterno. Questo è un altro buon motivo per utilizzare il qualificatore const sia sul puntatore e sulla parte appuntita del tipo.

Tutto sommato, utilizzando il qualificatore const per quanto possibile contribuirà a rendere il codice meno soggetto a errori, e può anche aiutare il compilatore di ottimizzare il programma risultante.

Usare un riferimento per questo tipo, tuttavia, non deve fare alcun cambiamento significativo, se questi tipi descrivono valori montaggio in un registro CPU (che è generalmente il caso),

Quindi, tutte le tre versioni del metodo dovrebbe riducersi allo stesso codice assembly generato.

Nel caso particolare di tipi di ritorno primitivi, non importa. il valore di ritorno può essere convertito avanti e indietro in uno const qualificato.

Altri hanno anche menzionato l'interesse del qualificatore const sulla funzione stessa. Anche se al di fuori della portata della domanda originale, dirò anche che è effettivamente meglio quando possibile (come per Foo::bar) qualificare una funzione come const.

+1

"aiuterà anche il compilatore a ottimizzare" - * sarà * un po 'forte. Ho fatto una domanda su http://stackoverflow.com/questions/1121791, molto tempo fa, e la risposta è risultata essere che la qualifica costante di una variabile primitiva ha aiutato gcc 3 a ottimizzare, ma gcc 4 era abbastanza intelligente da non aver bisogno Aiuto. –

+0

bene, forse. Per i tipi primitivi forse non importa molto. – didierc

Problemi correlati