Direi che qualsiasi variabile locale che non cambia dovrebbe essere constexpr se possibile e fare riferimento a const se non lo è. Se intendi modificare una variabile devi veramente sapere se si tratta di un riferimento o di una copia locale. Se ti sbagli, il tuo programma non farà ciò che ti aspetti che faccia. Tuttavia se la variabile è const non importa se si tratta di un riferimento o di una copia.
Il linguaggio supporta ciò consentendo a un const ref di collegarsi temporaneamente.
// some functions
int& by_ref();
int by_val();
int main(){
{
auto a = by_ref(); // takes a copy
const auto b = by_ref(); // also takes a copy
auto& c = by_ref(); // no copy
const auto& d = by_ref(); // no copy
}
{
auto a = by_val(); // might copy
const auto b = by_val(); // might copy
auto& c = by_val(); // error reference to temporary
const auto& d = by_val(); // allowed! no copy.
}
}
Si noti che a, be c si comportano in modo diverso a seconda che l'assegnazione sia per riferimento o per valore. Ma d è lo stesso in entrambi i casi.
Se una funzione restituisce un riferimento, d si limiterà a fare riferimento a quel valore, se restituisce un valore, quindi d si riferirà a un oggetto temporaneo, la durata di tale oggetto verrà estesa fino a quando d non sarà più disponibile.
Quindi usando const auto & laddove possibile il tuo codice è più robusto alle modifiche nel resto del codice. È possibile modificare il valore restituito dal riferimento al valore senza rompere il codice e dal valore al riferimento senza incorrere in una copia aggiuntiva.
Se costruisci un'istanza di una classe nella tua funzione allora usare un riferimento const è un po 'strano e potrebbe confondere qualcuno che lo sta leggendo.
Per un tipo integrato è già stato sottolineato che probabilmente non c'è alcun vantaggio in termini di prestazioni per la marcatura di const, ma per un tipo più complesso ci sono buone ragioni per farlo. Considera quanto segue.
class my_class{
protected:
struct my_data {
int x, y, z;
};
~my_class() = default;
private:
virtual my_data& data() = 0;
virtual const my_data& data() const = 0;
public:
auto& x() {
return data().x;
}
auto& y() {
return data().y;
}
auto& z() {
return data().z;
}
auto& x() const{
return data().x;
}
auto& y() const{
return data().y;
}
auto& z() const{
return data().z;
}
};
Noterai che ho definito tutto in my_class due volte. Sono un tipo pigro, quindi non ho intenzione di farlo senza una buona ragione. Il motivo è che un'istanza const di my_class può accedere solo ai metodi etichettati const.
Quindi ho bisogno di fornire sia le versioni const che non const di alcune funzioni e il mio istinto naturale è di chiedermi se ho davvero bisogno di fare tutto ciò che digita.
È abbastanza ovvio per questa versione che abbiamo bisogno delle versioni const di tutte le funzioni. È chiaramente un'astrazione e non posso sapere come verrà implementata o utilizzata. È ragionevole presumere che qualcuno vorrà un const my_class da qualche parte e dovrebbero comunque essere autorizzati a utilizzare tutti i metodi.
Tuttavia, come di solito preferisco astrarre attraverso i template, a volte ho una piccola classe che userò solo localmente o come soluzione per qualche strano sintassi, quindi può essere davvero allettante fornire solo il non versione const di una funzione e utilizzare solo istanze non const.
Che funziona benissimo finché non smette di funzionare. Quando ad un certo punto da qualche parte una versione const di qualcosa crea un'istanza di una versione const della mia classe otteniamo uno di quei messaggi di errore stile guerra e pace che i meta-programmatori template amano così tanto.
Quindi la morale è sempre definire le versioni const e non const delle funzioni membro. I motivi sono esattamente gli stessi dell'esempio my_class, ma non sempre sono del tutto ovvi in ogni contesto. Sfortunatamente se non è ovvio il motivo per cui dovresti fare qualcosa, di solito non è ovvio che non farlo sia ciò che ha causato un errore o un bug.
Quindi, se dichiarate sempre variabili come const a meno che non sia necessario modificarle
catturerai qualsiasi classe che non ha versioni const delle sue funzioni. Ciò significa che sarai meno riluttante a metterli in primo piano perché sai che capirai l'errore abbastanza presto da doverlo risolvere da solo.
Anche se non hai definito la classe tu stesso, potrebbero esserci versioni leggermente diverse di alcuni metodi per const e non const.
In genere non dovrebbe essere il caso che le funzioni di membro const e non const di una classe sono drammaticamente diverse ma non c'è nulla nella lingua che dice che devono essere uguali. Quindi dichiarando una variabile locale const si potrebbe avere un comportamento leggermente diverso ed è probabilmente il comportamento const che si desidera. L'unico esempio a cui riesco a pensare dove potrebbe incorrere in un colpo di performance è in una situazione multithread. L'accesso a una funzione membro non const potrebbe implicare l'adozione di un blocco di scrittura che dovrebbe attendere l'uscita di tutti i lettori, ma la versione const potrebbe richiedere solo l'arrivo di uno scrittore.
Quindi ci sono buone ragioni per preferire const in molte situazioni, sebbene per i tipi integrati e alcune piccole classi gli argomenti non sono altrettanto forti. In questo caso, in questo caso, raccomanderei di applicare la regola di non-ottimizzazione preferendo sempre const piuttosto che fare alcune eccezioni senza grandi vantaggi.