3

If I have some code which assigns a value to a variable, and that value then does not change, is it considered good practice to use const during the assignment?

e.g.

void foo()
{
   int n = some_function_which_computes_n(); // <-- this could (should?) be const

   // imagine some code here which uses n, but doesn't change it
}

I would use const int n here, but most of my co-developers think it's a bit of a waste of time and typing. They see no reason to go the extra mile.

Is there any compelling reason to do so or it is more a matter of personal taste? Does it help the compiler in any way produce better code or is it borderline defensive coding? I know it's technically probably more correct to use const here, but that's not really compelling.

1
9

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.

3
  • Regarding your example: It could also put k in the code-segment, as main() cannot be re-entered, making the call explode if you were wrong and otherwise saving an instruction. – Deduplicator Aug 29 '17 at 17:35
  • 1
    @Deduplicator As said, the example is silly, just to show that const is not only optical. I edited it to clarify that const-casting is not so nice, but that starting to use more const, leads to cleaner code because you'll start to care about const or non const pointers and references. – Christophe Aug 29 '17 at 17:39
  • Programmers should carefully avoid undefined behaviors when using const_cast. – rwong Aug 30 '17 at 0:07
3

Using const consistently is, approximately, Static Single Assignment Form. (There’s more to it than that, but that’s the aspect it’s named after.) That’s what many compilers, including clang, translate code into as an intermediate step during optimization anyway, and it also converts easily to Continuation-Passing Style, which other compilers use.

You generally don’t care about making the compiler’s job easier, though. I personally find that it helps me write correct code.

Let’s suppose you make all variables (with some common-sense exceptions such as arrays for in-place algorithms and loop indices) const. If you need another intermediate local value, you define a new const variable for it. The optimizer will be able to see when you first and last use each local const variable, and determine its lifetime accordingly.

If you don’t alter the variable, there’s no loss of efficiency, and maybe even some optimization if the compiler could not have deduced already that the variable cannot change. In fact, you’re just doing the first optimization step yourself. The variable, throughout its lifetime, will only contain one thing and will never be uninitialized or changed. It’s a simple invariant, one less thing that can go wrong. If you do alter the variable, that’s a logic error and the compiler will catch it now. Without const, you’d have a bug when the variable contained something other than you thought it would, and only checking the value in the debugger would tell you about it.

1

I'll start by saying that I mostly agree that using it is good. That said, I do run into 1 downside from time-to-time. I work on a codebase where one developer used const a lot. It was mostly a good idea and his code is pretty good and easy to work with. He would write stuff like this:

const int someVal = 1;
const int someOtherVal = 2;

for (int i = 0; i < maxVal; i++)
{
    ...some code...
    callFunction(someVal, someOtherVal);
    ...more code...
}

Nothing out of the ordinary. But now I have to modify the code in such a way that someVal's value is based on a conditional. Normally, I'd just change it to:

if (condition)
{
    someVal = 1;
}
else
{
    someVal = 2;
}

But because it was declared const, I can't make that change. I could use a ternary if the condition is reasonable:

const int someVal = condition ? 1 : 2;

But often the condition is a long expression, or involves calculating something. So the options are un-const it, or call a function to calculate the value:

const int someVal = figureOutSomeVal(<some set of values>);

Now I'm writing more code and passing arguments that I can screw up, etc.

Doing this once is no big deal. But when there's a major change and suddenly all the const things now have variable definitions, it can be a pain. I can definitely see some developers not liking having to deal with that.

That said, I think it's worth the possible future pain to use const anyway, in most cases.

2
  • promoting const int someVal = figureOutSomeVal(...) is a feature, not a bug, of "const everywhere" style – Caleth Aug 30 '17 at 8:35
  • 1
    No disagreement here. My point is mainly that developers who dislike const generally dislike it because it makes slightly more work than not using it. At least that's the only remotely valid claim against it that I've heard. I thought it was worth mentioning even though I disagree with it. – user1118321 Aug 30 '17 at 16:26
0

No performance benefit, but it is good practice particularly in collaboration.

Can also be useful to prevent accidental that might occur when something is passed by reference.

I would recommend it if it's something that needs to stay the same, but if it's used very locally and eventually gets trashed then it's less important.

Not the answer you're looking for? Browse other questions tagged or ask your own question.