Ci sono diversi vantaggi:
- chiarisce la tua intenzione: tutti capiranno che questa variabile non dovrebbe variare.
- evita errori stupidi, in cui un errore di battitura avrebbe causato la modifica del valore
- forza la disciplina: se usi puntatori o riferimenti a quella variabile, il compilatore si lamenterà se si tratta di un puntatore non const o di un riferimento, che potrebbe portare a sottili errori.
- inoltre, ciò incoraggerà la disciplina nel valutare la costanza degli argomenti passati per riferimento o tramite puntatori, che alla fine eviteranno errori e codici più sottili che è più facile da verificare.
- consente al compilatore di formulare assunzioni e ottimizzazioni, che altrimenti non potrebbero essere fatte in determinate circostanze.
Lascia che sviluppi l'ultimo punto con un esempio piccolo e sciocco.
L'approccio non const :
extern void f(int *p); // function f() takes a non const pointer
int main()
{
int k = 10;
f(&k); // the compiler has to assume that f() could change k
int i = k*k+2*k+1; // the compiler has to make the operation
std::cout<<i;
}
La parte rilevante di l'assemblaggio ha il seguente aspetto:
mov DWORD PTR [rsp+12], 10 // store value in k
call f(int*)
mov eax, DWORD PTR [rsp+12] // load the potentially changed k
lea esi, [rax+2] // and make the computation
imul esi, eax
add esi, 1 // here we are ready to display value
Qui il compilatore deve presupporre che k possa essere modificato da f()
, perché questa funzione è sconosciuta. Quindi il codice generato deve fare il calcolo di i
, anche se sapessimo che f()
non cambia il valore puntato a.
L'approccio const:
int main()
{
const int k = 10;
f(const_cast<int*>(&k)); // ATTENTION: we have to be sure about the cast!!
int i = k*k+2*k+1;
std::cout<<i;
}
Innanzitutto, il const_cast
indica che stiamo correndo un rischio. Quindi dobbiamo essere veramente sicuri che f()
non cambierà il valore puntato a (se non sarebbe un comportamento indefinito).
Ma il compilatore potrebbe quindi generare un codice molto più veloce:
mov DWORD PTR [rsp+12], 10
call f(int*)
mov esi, 121 // the compiler could make the calculation upfront!
L'approccio clean const:
Ovviamente const_cast
è qualcosa di rischioso, e dovrebbe essere evitato se possibile. Ed è qui che compare il vantaggio di secondo livello dell'uso di const : diventi sensibile a questo tipo di sottigliezze e inizierai ad aumentare la const
disciplina, riscrivendo f()
correttamente rendendo esplicita la costanza bene:
extern void f(const int *p); // if parameter remains const, tell it !
int main()
{
const int k = 10;
f(&k); // the compiler knows that k will not change
int i = k*k+2*k+1; // the compiler knows it's 121
std::cout<<i;
}
Questo codice è un evento migliore, perché non solo sei sicuro che k sia costante, ma tutti ora sanno che f()
non cambia il valore puntato e il compilatore può fare una propagazione costante migliore.
Naturalmente, passare i parametri per riferimento produrrebbe risultati simili.