More const — fewer bugs

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.

This entry was posted in programming and tagged , , . Bookmark the permalink.

11 Responses to More const — fewer bugs

  1. Bartek F. says:

    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:

      std::string populate_name(std::string name)
      {
        // populate name with contents
        return name; // 'name' treated as rvalue 
      }
      

      C++ has a special rule that says: if types match and we have a simple return object; statement, then name object should be treated as rvalue. So the move constructor is a better match here. But if you add a const to your variable:

      std::string populate_name(const std::string name)
      {
        // populate name with contents
        return name;
      }
      

      name in return statement now has a type std::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:

      std::string populate_name(const std::string name)
      {
        // populate name with contents
        return std::move(name);
      }
      

      In this case the return type of std::move is std::string const&&.

  2. einpoklum says:

    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.

  3. wrazik says:

    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.

  4. Swithch_To_Linux says:

    Nothing is perfect…

  5. Pingback: C++ annotated: Sep – Dec 2016 | CLion Blog

  6. Pingback: C++ annotated: Sep - Dec 2016 - ReSharper C++ BlogReSharper C++ Blog

  7. Kot Letov says:

    “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”;

  8. D S says:

    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

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.