Just make everything const
that you can! That's the bare minimum you could do for your compiler!
This is a piece of advice, many senior developers tend to repeat to juniors, while so often even the preaching ones - we - fail to follow this rule.
In this series of articles, we'll discuss about:
const
functionsconst
local variablesconst
member variablesconst
return types-
const
parameters
We already covered the last but one topic. Today we finish by stating when we should use const parameters. Let's differentiate between plain old data types and classes.
Const POD parameters
In this section, we talk about the primitive data types, such as bools, ints, floats, chars and alike.
Should they be taken as const parameters?
They should not be passed as const references or pointers. It's inefficient. These data types can be accessed with one memory read if passed by value. On the other hand, if you pass them by reference/pointer, first the address of the variable will be read and then by dereferencing it, the value. That's 2 memory reads instead of one.
We shall not take a POD by const&
.
But should we take them simply by const?
As always, it depends.
If we don't plan to modify its value, yes we should. For better readability, for the compiler and for the future.
void setFoo(const int foo) {
this->m_foo = foo;
}
I know this seems like overkill, but it doesn't hurt, it's explicit and you don't know how the method would grow in the future. Maybe there will be some additional checks done, exception handling, and so on.
And if it's not marked as const, maybe someone will accidentally change its value and cause some subtle errors.
If you mark foo
const, you make this scenario impossible.
What's the worst thing can happen? You'll actually have to remove the const qualifier, but you'll do that intentionally.
On the other hand, if you have to modify the parameter, don't mark it as const.
From time to time, you can see the following pattern:
void doSomething(const int foo) {
// ...
int foo2 = foo;
foo2++;
// ...
}
Don't do this. There is no reason to take a const
value if you plan to modify it. One more variable on the stack in vain, on more assignment without any reason. Simply take it by value.
void doSomething(int foo) {
// ...
foo++;
// ...
}
So we don't take PODs by const&
and we only mark them const
when we don't want to modify them.
Const object parameters
For objects, there is another rule of thumb. If we'd take a class by value as a parameter, it'd mean that we would make a copy of them. In general, copying an object is more expensive, then just passing a reference around.
So the rule to follow is not to take an object by value, but by const&
to avoid the copy.
Obviously, if you want to modify the original object, then you only take it by reference and omit the const.
You might take an object by value if you know you'd have to make a copy of it.
void doSomething(const ClassA& foo) {
// ...
ClassA foo2 = foo;
foo2.modify();
// ...
}
In that case, just simply take it by value. We can spare the cost of passing around a reference and the mental cost of declaring another variable and calling the copy constructor.
Although it's worth to note that, if you are accustomed to taking objects by const&
you might have done some extra thinking whether passing by value was on purpose or by mistake.
So the balance of extra mental efforts is questionable.
void doSomething(ClassA foo) {
// ...
foo.modify();
// ...
}
You should also note that there are objects where making the copy is less expensive or on a comparison to the cost of passing a reference about. It's the case for Small String Optimization or for std::string_view
. This is beyond the scope of this article.
For objects, we can say that by default we should take them by const reference
and if we plan to locally modify them, then we can consider taking them by value. But never by const
value, which would force a copy but not let us modify the object.
Conclusion
In this series, we saw when and how to use the const
qualifier for functions, for return values, local and member variables and finally today for function parameters.
For function parameters, the rule is different for plain old data types and for objects. We tend to take primitive data types by value, and objects by const&
.
Connect deeper
If you found interesting this article, please subscribe to my personal blog and let's connect on Twitter!
Top comments (4)
Hello,
I have 2 remarks:
1/ "POD" has a specific meaning (until C++20). "POD" is not a synonym for "primitive data types" (I have just learned that the dedicated name seems to be "Fundamental types" ^^)
See:
2/ It's worth noting that
const
for parameters is only relevant for functions definitions, not function declarations.For example, the following code is valid:
clang-tidy warns about it:
Personally, I don't use
const
for base types. But for other typesconst&
is of course the default way :)Thanks, for your remarks!
I used "POD" for Plain Old Data, but indeed I should have used Fundamental types. We both learned something :)
Your second point is wow, interesting... Even without clang-tidy, I'd warn anyone to not match the declaration with the definition. I'd find it really disturbing :(
I agree, that for small functions, using const value parameters don't add much readability, but in case of longer ones, it gives some extra safety and then I prefer to be consistent.
Thanks again for your captivating remarks!
You can learned new stuffs everyday, and I like that :)
Interesting indeed... "Scary" sounds more accurate to me 😅
In fact, there are other scary stuffs regarding declarations, definitions and redeclaration (yes, you can re-declare a function without exactly matching the previous declarations). Since you live in France, I suppose you speak French so take a look at the section "Le grand bazar des redéclarations" in this article I wrote about default parameters: "Arguments par défaut d'une fonction en C++". You will get psyched 😂
You're welcome! Glad you found them interesting :)
Thanks, I'm going to read it during the weekend. I just had a glance at it, and yeah, the default parameters for virtual can be problematic. I also wrote a draft about it after we had discussed that in our team a few weeks ago when we activated a new Sonar profile...
By the way, luckily the const mismatch doesn't work with references or pointers to const data.