To bother or not to bother... "const" as function argument

Started by
31 comments, last by mgarriss 18 years, 10 months ago
Here's a case direct from stuff I'm working on right now...

I took over ownership of a large body of code that has to support multithreading and be rock-solid. The code has evolved from single-threaded code over a period of 15 years or more. It's painfully clear by looking at it that multi-threading was added here and there as necessary, parts of it were well thought out for the conditions at the time and parts were just crammed in. As time went on the whole thing became a mess that was basically impossible to reason about as a whole. In particular everytime some multi-threading problem came up locks were arbitrarily inserted which solved the immediate problem but causing a wide variety of problems down the road.

My job (part of it anyway) is to clean up the mess. The first thing I did was go in and make large parts of the code const-correct. The resulting code churn is huge but mostly mechanical but more importantly is that I know what state of what objects need to be protected under what scenarios. I now have some reasonable assurance that I can change the locking to be sane and understandable by mere mortals. This would have been essentially impossible without const.
-Mike
Advertisement
Quote:Original post by Fruny
The first function wouldn't be able to handle a const, literal, or unnamed temporary parameter. You're pointlessly restricting the kind of data it can deal with.


I'd like to re-iterate this point, since it can bite you. The problem is that non-const-correct code simply doesn't play well with const-correct code. Consider the following contrived example:

struct Vector3{    Vector3( float X, float Y, float Z ) : x(X), y(Y), z(Z) {}    float x, y, z;};class Object{    public:        const Vector3& GetPosition() const { return m_position; }        //....etc    private:        Vector3 m_position;}void Print( Vector3& v ){    printf( "%f, %f, %f", v.x, v.y, v.z );}int main(){    Vector3 pos( 1.0f, 2.0f, 3.0f );    Print( pos ); // This works fine    Print( Vector3( 1.0f, 2.0f, 3.0f ) ); // Oops! Compile error    Object obj;    Print( obj.GetPosition() ); // Oops!  Compile error        return 0;}


So basically, even if you don't care whether you functions modify the object or not, you will have problems if the other people you work with write const-correct code that needs to call your functions. In addition, you preclude yourself from being able to use temporaries.

-John
- John
Quote:Original post by Teknofreek
So basically, even if you don't care whether you functions modify the object or not, you will have problems if the other people you work with write const-correct code that needs to call your functions. In addition, you preclude yourself from being able to use temporaries.

your point seems to be that you can't have a program or lib that is half-const-correct. this is of course a great point. i've also run into the problem of trying to keep my/our code const-correct while having to interface with libs that where not const correct. this is a serious pain is the ?!#. it's all or none i think. it takes additional dicipline. for me it allows the compiler to help my already overworked brain.

_ugly
Quote:Original post by mgarriss
Quote:Original post by Teknofreek
So basically, even if you don't care whether you functions modify the object or not, you will have problems if the other people you work with write const-correct code that needs to call your functions. In addition, you preclude yourself from being able to use temporaries.

your point seems to be that you can't have a program or lib that is half-const-correct. this is of course a great point. i've also run into the problem of trying to keep my/our code const-correct while having to interface with libs that where not const correct. this is a serious pain is the ?!#. it's all or none i think. it takes additional dicipline. for me it allows the compiler to help my already overworked brain.

_ugly
Yet another reason why everyone should do it correctly.
btw that is what const_cast is for.

Guys, listen to Fruny he knows what he's talking about!

Do you want to be just some average joe programmer, or a top-notch programmer? All the experts do it, so why shouldn't you? Make your programs const-correct and people reading your code will have one more reason to think you're a top-notch programmer.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
another important thing to understand ... you do not make a thing const or not const based on whether or not is changes the object ....

you make it const or not const based on whether it PROMISES NOT TO CHANGE THE OBJECT or not ...

you add const when you are adding a restriction to the contract, saying in effect "under the rules of our current contract I promise not to mondify this object".

There are many functions that will not modify their object's in some cases, but will in others. For instance if your game world has mobile and immobile objects, the mobile object's will be modified when they colid with other objects, the immobile object's will not ... but the immobile object's collision handling functions still DO NOT use the const modifier on their parameter, because the contract for object's in general does not promise const-ness.

Now of course, while designing the program it may turn out that you will need to modify the object where you thought you would never need to (in which case, your initial contract is no longer valid). The reason const helps in these cases, is as a warning, hopefully causing the programmer who is about to remove it to consider carefully what they are doing, and to use "find in files" type analysis of client code to make sure such a design change is safe. If you had never used the const modifier in the first place, you wouldn't usually realize you are making a fundamental contract change in your object model.
I would use const reference for efficiency reasons. Otherwise I pass by value to be compact and have less typing to do. I would recommend the use of const in function param if going for speed because that signifies the function won't be changing the data passed in.Also, mark your functions as const if you think they won't modify the object. That way you know quickly that the function isn't changing object and problem probably lies elsewhere. These are all good debugging tips.
If I use const in my code and then want to use your code I'll have to cast my const correct objects if I want to use your functions.

I wouldn't know that you weren't going to change my objects, unless you'd documented it, so I'd be unlikely to use your functions.

You might as well document it in the code by putting const.

It will also alleviate me having to put const_cast<SSomeStruct&> everywhere I want to call your functions.

So it's a friendly thing to do as much as anything. And will likely increase the chances of people wanting to touch your code with a barge pole.
Quote:Whether the local copy can be modified or not is irrelevant. Whether the object contains pointers or not is equally irrelevant.


No it isn't.

struct Bar{   int* getI(){return i;}   int const* getI()const{return i;}private:   int* i;}void foo1(Bar b){    *(b.getI()) = 5;//oops, we've changed a pass by value}void foo2(const Bar b){    *(b.getI()) = 5;//won't compile}


That's also the same reason that passing pointers by value is different to passing pointers by const value.

[Edited by - Nitage on June 12, 2005 5:11:27 AM]
Quote:Original post by Nitage


Apart from the mistake in your code, passing by const value would stop the pointer being changed but would not stop the pointed to value being changed.
Quote:Original post by petewood
Apart from the mistake in your code, passing by const value would stop the pointer being changed but would not stop the pointed to value being changed.


Fixed now.

This topic is closed to new replies.

Advertisement