Const Correctness Part 2

by Darren Collins
Tuesday, 23 March 2004

Last week's article discussed the use of the keyword const in function parameter lists. I had a few readers write in asking why I only covered three forms of const parameters, when there are several more legal possibilities.

In order to explain those other forms (and why not to use them), I need to first explain some more about declaring const pointers.

int* i;

This is the simplest declaration of a pointer to an integer. You can later change the value of the integer that i points to using dereferencing, and you can make i point to a different integer in memory.

const int* i;

As last week's article discussed, this form declares a pointer to a constant integer. You can later make i point to another integer, but you can't change the value of the integer that i points to.

int* const i;

This form declares a constant pointer to an integer. You can't make i point to another integer, but you can modify the value of the integer i points to.

const int* const i;

This declares a constant pointer to a constant integer. You can't make i point to another integer, and you can't change the value of the integer i points to.

It's also worth noting that 'int const* i' is a legal declaration, and means the same as 'const int* i'. This form is unusual, though, and should be avoided to keep your code readable.

Now that you know what the different placements of const mean in a pointer's declaration, we can look at using these new forms in the parameters to functions.

void f3(int* i);

This was the basic pointer version of the function declaration we built const correctness onto. It allows the function to change the value of the object pointed to by i, but won't let you make i point to another object. That's because a copy of the pointer is actually created and passed into the function when it's called - so when the program returns, the temporary copy of the pointer is destroyed and the original pointer remains unchanged.

void g3(const int* i);

This function was discussed last week. Just like f3() above, the pointer i can't be made to point to another object. In addition, it assures the caller that the object pointed to by i will not be modified.

void g4(int* const i);

This function declaration promises that it will not make i point to another object, but it can change the value of the object pointed to by i. From the caller's point of view, that's exactly the same promise as f3() above.

For the person who writes the implementation of g4(), though, it means that they can't make the copy of i point to another object, even though that change would only affect the copy and not the caller's original i. Use f3()'s form unless you have a specific reason to prevent the copy of i inside the function from pointing to another object.

void g5(const int* const i);

This function promises that the object i points to won't be changed, and that i won't be made to point to another object. To the caller, that's the same promise as g3() above.

For the programmer writing the code inside g5(), it also means they can't make the copy of i inside the function point to another object (similar to g4() above). In general, don't use g5()'s form unless you have a specific reason to impose this extra restriction on the implementor.

void g1(const int i);

This form was covered last week, and promises that the value of i won't be changed inside the function. From the caller's point of view, it's the same as the function f1(int i).

The only difference is that the person who writes the function's implementation can't modify the copy of i inside g1(). Sometimes modifying the copy of i inside the function could simplify an algorithm (knowing that it won't change the caller's i), but occasionally this extra restriction is desirable. Avoid this form unless you have a specific reason to prevent modification of the copy of i. Use f1() instead.

void g6(int& const i);

This function prototype promises that the reference i won't be made to refer to a different object inside g6(). References can't ever be bound to a different object anyway, so this form is completely redundant. Don't ever use it!

Summary

In general, avoid the parameter forms:

void g1(const int i);
void g4(int* const i);
void g5(const int* const i);

Only use them if you have a really good reason to restrict the internal implementation of the function. Even then, you may be better off documenting the reason for the restriction in a comment inside the function, and using the relevant alternate forms (f1(), f3() and g3() respectively) for the interface. That will avoid confusing programmers using your functions who aren't aware of the subtle differences outlined above, while still providing well-documented code for future maintainers.

Never use the form:

void g6(int& const i);

It's always redundant, and will only make your interface more confusing to the calling programmer.

 


Related Articles
Index
- Links - C/C++
- Code Layout Styles
- Const Correctness Part 1
- Const Correctness Part 2
- Const Correctness Part 3
- Const Correctness Part 4
- Const Correctness Part 5
- Const Correctness Part 6

This site Copyright 1999-2005 Darren Collins.