I have just read article “Please declare your variables as const” by Bartłomiej Filipek. It argues that declaring your variables const
is beneficial for reasons other than performance. In particular, it can help find bugs in your code. Now, let me illustrate this claim by showing how the const
could help find the bug from my previous post.
Let’s see the buggy code again:
void process_names() { const char * fileName = "contents.txt"; const char * foundName = 0; // to be assigned to later Names names = parse_file(foundName); foundName = find(names); // ... }
This program is expected to open file contents.txt
, read names from it; then find a desired name, and do something with it. But the program will not do it, because I have a bug in this code. Accidentally, I have passed the wrong pointer to function parse_file
. I confused them because names were so similar (I didn’t notice that my IDE suggested the wrong name in the hint); the types happened to match…
Now, let’s see how the situation changes when I apply the principle “mark your variables as const, wherever possible”:
void process_names() { const char * const fileName = "contents.txt"; const char * const foundName = 0; // to be assigned to later Names names = parse_file(foundName); foundName = find(names); // ... }
I have annotated the two pointers as const
. With pointers, this is a bit tricky: you have to remember on each side of the pointer to put it.
Now, my program fails to compile: I am trying to assign an address to a constant pointer. Thus, I am forced to apply another principle: “declare your variables as local as possible”. I will move the the declaration of foundName
down to line 7:
void process_names() { const char * const fileName = "contents.txt"; Names names = parse_file(foundName); const char * const foundName = find(names); // ... }
Now, I get another error: I am reading from variable foundName
before it is declared. But I never wanted to read it there! And now, the compiler has brought my attention to the misused variable. Only because I decided to make my variables const.
The connection is somewhat accidental; but for some subtle bugs, such accidental discoveries might be the best option. The more opportunities you will make for these “accidents”, the more chances you have of finding the bug.
Thanks for this example Andrzej! In my article, I’ve only mentioned that using const can reduce the number of potential bugs, but I didn’t show any sample…
BTW: in the comments to the blog posts I’ve noticed that one argument against using const is that it prevents from using move semantics. Have you come across such issues somewhere? Of course, it’s not a problem for objects that cannot be moved or simple types: like pointers, integers, value types…
Indeed, I have. By “move semantics” I understand: providing move constructors for types where it makes sense, and then provide means for selecting this move constructor where applicable. The second part is usually implemented in the language through rvalue references: if an expression result can be bound to an rvalue reference, the move constructor gets selected:
C++ has a special rule that says: if types match and we have a simple
return object;
statement, then nameobject
should be treated as rvalue. So the move constructor is a better match here. But if you add a const to your variable:name
in return statement now has a typestd::string const&&
and move constructor cannot bind to it because of the const, so the copy constructor gets chosen. You cannot move the guts of the const object. Copy elision also does not apply here. You cannot elide between const and non-const object.Even if you add an explicit
move
, it does not help:In this case the return type of
std::move
isstd::string const&&
.hmm… I’ve replied by creating another post on that 🙂
http://www.bfilipek.com/2017/01/const-move-and-rvo.html
but basically if we’re just talking about variables inside the function body, there’s not so much problem with move and RVO. But when you want to explicitly move from a variable, then applying const is not a good idea.
I hope I covered most of the cases 🙂
Mutable data is certainly a pain. Languages with no imperatives which changes variable values are usually more elegant and less bug-prone (although less flexible). This seems counter-intuitive to most people, but them when you study something like, say, ML, you suddenly realize you don’t really need all those variables, and most of them were just crutches used to express something which does involve variation over time; and you can mostly stick to “let x = foo in bar(x)”, which is about the same as having a const variable.
PS – Just noticed this blog while looking for your optional repository URL. I’m a fan.
I’ve finally found the perfect blog – thanks Andrzej! It was great to hear you on code::dive, I hope you will be speaker in the next year.
Thank you. I am glad you like it.
Nothing is perfect…
Pingback: C++ annotated: Sep – Dec 2016 | CLion Blog
Pingback: C++ annotated: Sep - Dec 2016 - ReSharper C++ BlogReSharper C++ Blog
“With pointers, this is a bit tricky: you have to remember on each side of the pointer to put it.”
No tricky if you follow rule “const is always right”. No need to remember.
char const * const fileName = “contents.txt”;
Well, actually you don’t need “mark your variables as const, wherever possible” principle. The only thing you need is “declare your variables as local as possible” since you use it in any case