Const correctness

| 2 Comments

Interesting thread over on Joel today about using const, or not.

The original poster asked how many people bothered to mark function parameters that they don't modify as const and whether there was any performance or readability advantages in doing so.

Ignore any performance issues, making your code const correct has massive readability and comprehension advantages. I'd go so far as to say it's one of the most important coding standard issues there is...

Uses of const
In C++ you can restrict the operations that you can perform on a type by specifying that it's const. Using const correctly and consistently means that when you look at a piece of code you can be certain that some types are not modified by the code that you're looking at or by code it calls. This can make code easier to understand because the person who wrote the code has indicated their intent as precisely as possible; you don't have to wonder if the call to Foo() can modify the bar object because it's declared as const. Likewise, in the code below, you can tell that this function doesn't change the values in pData, what's more, you also know that any functions that it calls with pData as an argument wont change the values in pData.

void CStreamProcessor::Write(
   const BYTE *pData,
   size_t dataLength)
{

If you have a variable that isn't variable then you can mark it const and tell your readers that it can't and wont change. If a class represents a trade and trade ids are never changed once assigned you might make the trade id const. Nobody can change the trade id by mistake and you're burning a small piece of business knowledge into the code.

I personally quite like using short lived, const variables as a way of documenting code; rather than writing this:

   CAsyncSocketConnectionManager manager(
      ioPool, 
      socketAllocator, 
      bufferAllocator, 
      false);                // don't post zero byte reads

I might, instead, do this:

   const bool postZeroByteReads = false;

   CAsyncSocketConnectionManager manager(
      ioPool, 
      socketAllocator, 
      bufferAllocator, 
      postZeroByteReads );

The advantage is that the comments can rot, especially poorly phrased comments on boolean parameters ;)...

If you have an object with a function that doesn't modify the state of the object then you can mark it as const. If you do this you can call the function on objects that are const; functions that aren't marked as const can only be called on non-const objects...

Function parameters that are const work together with functions marked as const to restrict what the code can do to various objects. This can help you to understand a programmers' intent when you're reading their code; correct use of const helps to tighten up the contracts that are defined as the code is designed and written. You can say to your callers, "I promise I won't change this" and the compiler will keep you to your word, at compile time...

But, it's just noise

One of the comments on Joel compared const correctness to using throw specs in C++; I'm afraid he's wrong. C++ throw specs are a pretty useless feature which get checked at run time and result in your program terminating if you get it wrong; const correctness is checked at compile time therefore you know it's right...

But, they might be lying to me, they might cast it away

const_cast<evil *>(pSurprise)

Of course, like most things in C++, you can work around const if you really want to. Use of the old "C-style" casts allow you to do pretty much whatever you want with a memory location, and the C++ const_cast allows you to cleanly, and obviously remove constness. The fact that these things are possible, and sometimes even desirable, doesn't reduce the value of being const correct.

But, not everyone is as anal about it and when my code meets their code it's hard

As some of the comments on Joel pointed out, because everyone isn't always using const correctly all the time sometimes you'll come across a piece of code that you have to use but where the programmer didn't use const correctly. When your const correct code meets their code you need to work out how to handle the situation. Firstly if the source is available and the programmer works in your organisation you should try and convince them of the "one true way" ;), if the code can't be changed then you need to do a little work to use the code. If the parameter to their code should be const, but isn't, i.e. they never modify it then you will need to use a const_cast to remove the constness of the type that you need to pass in. If their code can modify the value but your usage of their code means that you don't care about the result of the modification then you will need to create a non-const instance of that type, copy your const type to the non-const instance and pass that instead. This can be a pain but the pain can be eased by wrapping their code in a thin layer that does the necessary const correcting that your code requires. The layer and the collection of casts and comments will serve as a warning for other users. If you don't wrap the code and just use it as is, with casts littering your code, then you're failing to encapsulate your knowledge of their code and that's a much more serious crime than diluting the value of const correctness...

But, being const correct is too hard

When you write a piece of code you need to think. Being const correct means you need to think a little bit more when you write the code but you can trade that for thinking a little bit less when you read the code. Since code is read more times than it's written it's well worth being disciplined when you write code.

The extra thinking required isn't even that much; will I change the value of this object? Yes or no. If yes then the object can't be const, if no then it can. Likewise, will this function change the state of the object?

I don't really see why this is considered too hard a thing to do. When you write code you know what you will do with an object; if you are forever finding that you suddenly need to change an object from const to non-const and that the change causes ripples up the call stacks of the function you're working on then, perhaps, you should think a little more when designing? ;) Writing good code is hard, you DO need to think whilst doing it and this is a good thing.

And if you really want to be anal about it...

Usually const is only applied to reference parameters, this allows you to pass in an object by reference yet be sure that your object wont be changed by the code that you're calling. This is often more efficient than passing the object by value which, of course, gives the same guarentee... There's nothing to stop you marking value types used as parameters as const as well, it doesn't tell the caller anything but it does make it clear when reading the function that the code doesn't modify the value of the value type.

void CStreamProcessor::Write(
   const BYTE *pData,
   const size_t dataLength)
{

It's one less thing you need to think about when reading the code; dataLength is const, it can't change.

I find that the correct use of const makes code easier to read and understand. It forces the programmer to be more precise when writing the code and the fact that it limits what can be done to types means that you don't need to consider as many potential options when reading at a piece of code. Correct use of const tends to drive a design in the right direction; if some of the code needs to modify an object and some of the code doesn't then const will help you factor the code appropriately by building walls between areas of code that operate in different ways.

If I haven't managed to convince you of the value of const then you can learn more about const here:
http://www.gotw.ca/publications/advice98.htm
http://www.gotw.ca/gotw/006.htm
http://www.gotw.ca/gotw/081.htm

2 Comments

CAsyncSocketConnectionManager manager(
      ioPool, 
      socketAllocator, 
      bufferAllocator, 
      false);   // don't post zero byte reads

This is bit off topic, but I don't like using boolean flags to modify the behavior of a function. You inevitably end up with:


foo(bar, true, false, true, true, NULL);

And you can never remember what that means.

It is far better to use an enumerated value rather than a bool. Your solution is local. I think is better to force function users to use a self documenting style.

Microsoft is particularily bad at this. It seems every Win32 API has about 4 different behaviours depending on the flags passed to it.

Christopher

Chris

In general I agree, in code that you control it's better to use an enum, though I don't always do it... In situations where you can't change the code I find the localised solution helps.

Leave a comment