Are there any downsides to marking all variables you don't modify const?
There are no downsides to marking variables you don't modify const
.
There are some up-sides though: the compiler will help you diagnose when you unintentionally modify a variable you shouldn't/didn't mean to and the compiler may (although due to the language having const_cast
and mutable
this is rare) generate better code.
So, I'd advise; use const
where you can. There are no downsides and your compiler can potentially help you spot bugs. No reason not to (except for a bit of extra typing).
Note that this extends to member functions as well. Make them const
when you can - it lets them be used in more contexts and helps users reason about the code ("calling this function won't modify the object" is valuable information).
I can think of at least two downsides:
- verbosity: more words, more symbols to process, ...
- inertia: if you need to modify it, you'll have to go and remove this
const
and both are worth it.
Verbosity is an often heard argument against explicitness, however people often mistake reading speed with understanding speed. There is a balance to be found between verbosity and explicitness, certainly, too verbose may drown out useful information but too implicit/terse may not present information that has to be reconstructed/inferred/deduced/.
Personally, I use a strongly typed statically checked language so that the compiler picks out my mistake as early as possible; annotating with const
is both giving information to the reader and the compiler. I judge it worth the extra 6 symbols.
As for inertia, removing const
is probably only a small cost of the change... and it repays itself by forcing you to go through all the places where it's used and review the surrounding code to ensure it's actually alright to remove this const
. Suddenly modifying a particular piece of data in a code path where it previously was immutable requires ensuring that no part of the code path (or its callers) accidentally relied on this immutability.
Instead of this ¹non-standard code:
#import <string>
#import <iostream>
void example(const std::string& x) {
size_t length = x.length();
for (size_t i = 0; i < length; ++i) {
std::cout << x.at(i) << std::endl;
}
}
int main() {
example("hello");
}
… I'd write this:
#include <string>
#include <iostream>
using namespace std;
void example( string const& s )
{
for( char const ch : s )
{
cout << ch << '\n';
}
}
auto main()
-> int
{ example( "hello" ); }
The main place I could add const
, relative to the original code, was for the ch
variable in the loop. I think that's nice. const
is generally desirable because it reduces the possible code actions one has to consider, and range based loops let you have more const
.
The main drawback of using const
for most things, is when you have to relate to C APIs.
Then one just has to make some gut feeling decisions about whether to copy data, or trust in the documentation and use a const_cast
.
Addendum 1:
Do note that const
on a return type prevents move semantics. As far as I know this was first noted by Andrei Alexandrescu in his Mojo (C++03 move semantics) article in Dr Dobbs Journal:
” [A]
const
temporary looks like an oxymoron, a contradiction in terms. Seen from a practical perspective,const
temporaries force copying at destination.
So, this is one place where one should not use const
.
Sorry that I forgot to mention this originally; I was reminded by user bogdan's comment on another answer.
Addendum 2:
In the same vein (to support move semantics), if the last thing done with a formal argument is to store a copy somewhere, then instead of passing by reference to const
it can be better to use a non-const
argument passed by value, because it can be simply moved from.
I.e., instead of
string stored_value;
void foo( string const& s )
{
some_action( s );
stored_value = s;
}
… or the redundancy of optimized
string stored_value;
void foo( string const& s )
{
some_action( s );
stored_value = s;
}
void foo( string&& s )
{
some_action( s );
stored_value = move( s );
}
… consider just writing
string stored_value;
void foo( string s )
{
some_action( s );
stored_value = move( s );
}
It can be slightly less efficient for the case of lvalue actual argument, it discards the advantages of const
(constraints on what the code could possibly do), and it breaks a uniform convention of using const
wherever possible, but it doesn't perform badly in any situation (which is the main goal, to avoid that) and it's smaller and possibly more clear code.
Notes:
¹ Standard C++ does not have an #import
directive. Also, those headers, when properly included, are not guaranteed to define size_t
in the global namespace.