views:

279

answers:

5

I recently had an exchange with another C++ developer about the following use of const:

void Foo(const int bar);

He felt that using const in this way was good practice.

I argued that it does nothing for the caller of the function (since a copy of the argument was going to be passed, there is no additional guarantee of safety with regard to overwrite). In addition, doing this prevents the implementer of Foo from modifying their private copy of the argument. So, it both mandates and advertises an implementation detail.

Not the end of the world, but certainly not something to be recommended as good practice.

I'm curious as to what others think on this issue.

Edit:

OK, I didn't realize that const-ness of the arguments didn't factor into the signature of the function. So, it is possible to mark the arguments as const in the implementation (.cpp), and not in the header (.h) - and the compiler is fine with that. That being the case, I guess the policy should be the same for making local variables const.

One could make the argument that having different looking signatures in the header and source file would confuse others (as it would have confused me). While I try to follow the Principle of Least Astonishment with whatever I write, I guess it's reasonable to expect developers to recognize this as legal and useful.

+4  A: 

It makes the compiler do part of the work of catching your bugs. If you shouldn't be modifying it, make it const, and if you forget, the compiler will yell at you.

Yuliy
I agree - except that I sometimes feel that the only "bugs" caught by const flags are those where I forgot to mark something const.
Steve314
+3  A: 

If bar is marked const as above, then the person reading the code, knowing what was passed in, knows at all time exactly what bar contains. There's no need to look at any code beforehand to see if bar got changed at any point along the way. This makes reasoning about the code simpler and thus reduces the opportunity for bugs to creep in.

I vote "good practice" myself. Of course I'm also pretty much a convert to functional languages these days so....


Addressing the comment below, consider this source file:

// test.c++

bool testSomething()
{
    return true;
}

int test1(int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

int test2(const int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

In test1 there is no way for me to know what the value being returned will be without reading the (potentially sizable and/or convoluted) body of the function and without tracking down the (potentially distant, sizable, convoluted and/or source-unavailable) body of the function testSomething. Further, the alteration of a may be the result of a horrific typo.

That same typo in test2 results in this at compile-time:

$ g++ test.c++
test.c++: In function ‘int test2(int)’:
test.c++:21: error: assignment of read-only parameter ‘a’

If it was a typo, it's been caught for me. If it isn't a typo, the following is a better choice of coding, IMO:

int test2(const int a)
{
    int b = a;
    if (testSomething())
    {
        b += 5;
    }
    return b;
}

Even a half-baked optimizer will generate identical code as in the test1 case, but you're signalling that care and attention will have to be paid.

Writing code for readability involves a whole lot more than just picking snazzy names.

JUST MY correct OPINION
I would agree if you'd said "if `bar` is a reference or pointer and is marked `const`..."
greyfade
Expanded to explain with a non-reference, non-pointer example.
JUST MY correct OPINION
Why would you pass something by const value if you plan on copying it into a non-const value before you actually use it? You still can't tell what value will be returned without reading the function [which is what documentation is for anyhow], so the initial const-ness doesn't help you at all.
Dennis Zickefoose
Well, the main point of this is protection from brain death. The typo is more of an issue for me than the latter.Copying into a non-const value, however, allows me to do things like, say, involve that parameter in multiple calculations with each calculation knowing for a fact what the initial state is going to be -- no room for error with, say, an inserted line of code accidentally changing a value when it shouldn't be, etc.
JUST MY correct OPINION
But you've introduced the opportunity for accidentally using the wrong value. Obviously, if you *need* to keep both the original value around and a modified version, you have to create a second variable. But creating two variables just in case will make your code harder to read, not easier.
Dennis Zickefoose
Dennis: at the risk of starting a debate on immutable vs. mutable values, the primary reason to do this is because the original and modified values probably have different semantics associated with them.
Yuliy
+8  A: 

This has been discussed many times, and mostly people end up having to agree to disagree. Personally, I agree that it's pointless, and the standard implicitly agrees -- a top-level const (or volatile) qualifier doesn't form part of the function's signature. In my opinion, wanting to use a top-level qualifier like this indicates (strongly) that the person may pay lip-service to separating interface from implementation, but doesn't really understand the distinction.

One other minor detail: it does apply to references just as well as pointers though...

Jerry Coffin
+1 because I mostly agree, but all the standard says is that top-level const as used in the question is an implementation detail of the function, rather than being part of the interface. Saying the standard agrees that it's pointless is reading too much into it.
Roger Pate
@Roger: well, yes. Ultimately, the standard is (mostly) restricted to stating requirements, not making comments on style (though if you search for "self-immolation" you'll see that it occasionally ventures beyond that, and in what's officially a normative part of the standard at that! Someday I may have to work through the history to figure out which editor threw in that particular tidbit. :-)
Jerry Coffin
The most famous programming limerick. :)
Roger Pate
Johannes Schaub - litb
@Johannes: Quite honestly, I'm not sure what I was thinking when I wrote that, but it's obviously nonsense...
Jerry Coffin
+14  A: 

Remember the if(NULL == p) pattern ?

There are a lot of people who will tell a "you must write code like this":

if(NULL == myPointer) { /* etc. */ }

instead of

if(myPointer == NULL) { /* etc. */ }

The rationale is that the first version will protect the coder from code typos like replacing "==" with "=" (because it is forbidden to assign a value to a constant value).

The following can then be considered an extension of this limited if(NULL == p) pattern:

Why const-ing params can be useful for the coder

No matter the type, "const" is a qualifier that I add to say to the compiler that "I don't expect the value to change, so send me a compiler error message should I lie".

For example, this kind of code will show when the compiler can help me:

void bar_const(const int & param) ;
void bar_non_const(int & param) ;

void foo(const int param)
{
   const int value = getValue() ;

   if(param == 25) { /* Etc. */ } // Ok
   if(value == 25) { /* Etc. */ } // Ok

   if(param = 25) { /* Etc. */ } // COMPILE ERROR
   if(value = 25) { /* Etc. */ } // COMPILE ERROR

   bar_const(param) ;  // Ok
   bar_const(value) ;  // Ok

   bar_non_const(param) ;  // COMPILE ERROR
   bar_non_const(value) ;  // COMPILE ERROR

   // Here, I expect to continue to use "param" and "value" with
   // their original values, so having some random code or error
   // change it would be a runtime error...
}

In those cases, which can happen either by code typo or some mistake in function call, will be caught by the compiler, which is a good thing.

Why it is not important for the user

It happens that:

void foo(const int param) ;

and:

void foo(int param) ;

have the same signature.

This is a good thing, because, if the function implementer decides a parameter is considered const inside the function, the user should not, and does not need to know it.

This explains why my functions declarations to the users omit the const:

void bar(int param, const char * p) ;

to keep the declaration as clear as possible, while my function definition adds it as much as possible:

void bar(const int param, const char * const p)
{
   // etc.
}

to make my code as robust as possible.

Why in the real world, it could break

I was bitten by my pattern, though.

On some broken compiler that will remain anonymous (whose name starts with "Sol" and ends with "aris CC"), the two signatures above can be considered as different (depending on context), and thus, the runtime link will perhaps fail.

As the project was compiled on a Unix platforms too (Linux and Solaris), on those platforms, undefined symbols were left to be resolved at execution, which provoked a runtime error in the middle of the execution of the process.

So, because I had to support the said compiler, I ended polluting even my headers with consted prototypes.

But I still nevertheless consider this pattern of adding const in the function definition a good one.

Note: Sun Microsystems even had the balls to hide their broken mangling with an "it is evil pattern anyway so you should not use it" declaration. see http://docs.sun.com/source/817-6698/Ch1.Intro.html#71468

One last note

It must be noted that Bjarne Stroustrup seems to be have been opposed to considering void foo(int) the same prototype as void foo(const int):

Not every feature accepted is in my opinion an improvement, though. For example, [...] the rule that void f(T) and void f(const T) denote the same function (proposed by Tom Plum for C compatibility reasons) [have] the dubious distinction of having been voted into C++ “over my dead body”.

Source: Bjarne Stroustrup
Evolving a language in and for the real world: C++ 1991-2006, 5. Language Features: 1991-1998, p21.
http://www2.research.att.com/~bs/hopl-almost-final.pdf

This is amusing to consider Herb Sutter offers the opposite viewpoint:

Guideline: Avoid const pass-by-value parameters in function declarations. Still make the parameter const in the same function's definition if it won't be modified.

Source: Herb Sutter
Exceptional C++, Item 43: Const-Correctness, p177-178.

paercebal
I never knew you could change the const-ness of a parameter without resulting in a different function signature. The few times I've felt it useful for a non-reference parameter to be explicitly const, I defined a const reference inside the body of the function. so f(int i_) { const int } Might help if you need to use your anonymous compiler again.
Dennis Zickefoose
@Dennis Zickefoose: Your construct is interesting (I thought about a similar case, to hide non const local variable I needed to have initialized by passing them as references to other functions), but it does not remove the original variable (i_ remains accessible), and seems to be it is too much code to add for the produced value... For my anonymous compiler (who wrote Solaris CC?), I just added the "const" to the declaration, too... T_T ...
paercebal
Aaargh! I got bitten by the same compiler bug ONCE AGAIN! Despite the test script I did write, some functions apparently went undefined (and undetected) in the release build of my .SO, while they were correctly defined in the debug build. Of course, testers got the bug before it could be relase, but anyway... Thanks you, Sun Microsystems, for your broken compiler!
paercebal
+3  A: 

I tend to be a bit of a const fiend so I personally like it. Mostly it's useful to point out to the reader of the code that the variable passed in wont be modified; in the same way that I try to mark every other variable that I create within a function body as const if it's not modified.

I also tend to keep the function signatures matching even though there's not much point in it. Partly it's because it doesn't do any harm and partly it's because Doxygen used to get a bit confused if the signatures were different.

Len Holgate