There are several benefits:
- it clarifies your intention: everybody will understand that this variable is not supposed to vary.
- it avoids stupid mistakes, where a typo would have caused your value to change
- it forces discipline: if you use pointers or references to that variable, the compiler will complain if it is a non const pointer or reference, that could lead to subtle errors.
- furthermore, this will encourage discipline in assessing constness of arguments passed by reference or via pointers, which will ultimately avoid more subtle errors and code which is easier to verify.
- it allows the compiler to make assumptions and optimizations, that could otherwise not be made in certain circumstances.
Let me develop the last point with a small and silly example.
The non-const approach:
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;
}
The relevant part of the assembly looks like:
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
Here the compiler has to assume that k could be changed by f()
, because this function is unknown. So the generated code has to make the calculation of i
, even if we'd know that f()
doesn't change the value pointed at.
The const approach:
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;
}
First of all, the const_cast
shows that we are taking a risk. So we have to be really sure that f()
will not change the value pointed at (if not it would be undefined behavior).
But the compiler could then generate a much faster code:
mov DWORD PTR [rsp+12], 10
call f(int*)
mov esi, 121 // the compiler could make the calculation upfront!
The clean const approach:
Of course const_cast
is something risky, and should be avoided if possible. And that's where the second level benefit of using const appears: you become sensitive to this kind of subtleties, and you'll start increasing const
discipline, rewriting the f()
properly by making constness explicit as well:
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;
}
This code is event better, because not only are you sure that k is constant, but everybody now knows that f()
doesn't change the pointed value, and the compiler can do better constant propagation.
Of course, passing parameters by reference would produce similar results.