iammilind
iammilind

Reputation: 70100

Is it a good idea of maintaining "const-ness" as much as possible?

Recently, I have been developing a practice of making many things in my code as const:

(1) Arguments to function, which I know never going to be changed. e.g.:

void foo (const int i, const string s)
          ^^^^^        ^^^^^ 

(2) Return types as const. e.g.:

struct A {
...
  const int foo () { return ...; }
  ^^^^^
  operator const bool () const { return ...; }
           ^^^^^
};

(3) Trivial computation of integer or strings. e.g.:

const uint size = vec.size();
^^^^^
const string s2 = s1 + "hello ";
^^^^^

... and few more places. Typically in other real world codes, I don't see such small scale variables marked as const. But I thought, making them const will never harm. Is it a good programming practice ?

Upvotes: 11

Views: 2056

Answers (7)

Adrian Grigore
Adrian Grigore

Reputation: 33318

It definitely is, at least for code that needs to stay maintainable in the long run.

If you'd like to learn more about this, the book "Effective C++" by Scott Meyers shows numeous scenarios where you need const return types and and method arguments to secure your code against misuse.

Upvotes: 1

Frerich Raabe
Frerich Raabe

Reputation: 94529

(1) Arguments to function, which I know never going to be changed.

The only reason to make arguments passed by-value const is that you may want to avoid that you accidentally change the value of the copy inside your function, as in:

void f( int x ) {
  x += 2;
  // OOPS: At this point 'x' is actually not what the caller passed to us!
}

This cannot happen if you pass the argument as const int x. However, I know plenty of code where the programmer intentionally passes an argument as a value but not const, because he wants to modify the argument - otherwise he'd make an copy anyway. I.e. instead of

void f( const std::string &s ) {
    std::string s2 = s;
    s2[0] = 'A';
    // ...
}

they do

void f( std::string s ) {
    s[0] = 'A';
    // ...
}

The nice side effect of deliberately tinkering with the copy of the value like this is that you don't have to think about annoying naming issues (should the argument be s_ and the sanitized value is s? Or s2 vs. s, or what?).

Since the presence or absence of the const here doesn't influence the caller at all (you're getting a copy of the value anyway, so you can't tinker with the caller's data), it simply boils down to: sometiems it makes sense. Sometimes it doesn't. No good guideline here. My personal feeling is that you shouldn't bother though. If you want to make the argument const to avoid that you accidentally oevrwrite it, then maybe your function is too long in the first place (or the argument name is silly, like i or tmp or it something like that).

(2) Return types as const.

This only makes sense if you think there's a chance that callers of your code might accidentally attempt to modify the value returned by your function, like this:

QString f() { return "Hello"; }

If you'd like to forbid f.strip() or other mutating calls, you can make the return type const. I once knew a nice example for a case where returning an object as a non-const value actually allowed some fairly surprising client code. Unfortunately I cannot remember it anymore.

(3) Trivial computation of integer or strings.

This is actually exactly the same case as (1) which I discussed above. The same reasoning applies.

Upvotes: 1

Steve Jessop
Steve Jessop

Reputation: 279395

(1) and (3) are closely related. A by-value parameter is just a local variable with that name, as is the result of your computation.

Usually it makes little difference in short functions whether you mark local variables const or not, since you can see their entire scope right in front of you. You can see whether or not the value changes, you don't need or want the compiler to enforce it.

Occasionally it does help, however, since it protects you from accidentally passing them to a function that takes its parameter by non-const reference, without realising that you're modifying your variable. So if you pass the variable as a function argument during its life, then marking it const can give you more confidence that you know what value it has afterwards.

Very occasionally, marking a variable const can help the optimizer, since you're telling it that the object is never modified, and sometimes that's true but the compiler can't otherwise prove it. But it's probably not worth doing it for that reason, because in most cases it makes no difference.

(2) is another matter. For built-in types it makes no difference, as others have explained. For class types, do not return by const value. It might seem like a good idea, in that it prevents the user writing something pointless like func_returning_a_string() += " extra text";. But it also prevents something which is pointful -- C++11 move semantics. If foo returns a const string, and I write std::string s = "foo"; if (condition) s = foo();, then I get copy assignment at s = foo();. If foo returns a non-const string then I get move assignment.

Similarly in C++03, which doesn't have move semantics, it prevents the trick known as "swaptimization" - with a non-const return value I can write foo().swap(s); instead of s = foo();.

Upvotes: 14

user1084944
user1084944

Reputation:

Const correctness is, in general, important -- but the examples you are using are not really the places it matters. For example:

void foo (const int i, const string s)

I would be so bold as to declare this prototype wrong. If you just had the prototype

void foo (int i, string s)

then this is already a promise not to modify the values the user passes in for i and s. (since you get copies of those values, rather than references to the original) So, the only thing that const does in this case is to request the compiler to complain if you accidentally tried to modify i or s. But there are other ways to achieve this effect -- it doesn't really have any business being exposed externally.

Now, the following use of const would actually be useful:

void foo (int i, const string &s)

this retains your promise not to modify s, but now you get a performance benefit because it no longer involves a copy of s being made.

Some of the other uses are fine:

const size_t size = vec.size();

this is good. (once you replace uint with size_t) There isn't a performance benefit to it here, but it's self-documenting and can protect you against silly accidents.

const string s2 = s1 + "hello ";

This is good as well. Although again I might make it a reference instead:

const string &s2 = s1 + "hello ";

This one

operator const bool () const { return ...; }

is most irrelevant. You should probably implement operator bool instead of operator const bool even if for no reason other than the former is what people expect.

But it could matter for classes who have non-const versions. Returning const, then is not a matter of taste or style, but instead a declaration "I'm going to return to you a new object, but you're not allowed to call any of its non-const functions". This is usually a bad idea -- only make it const if you really mean to make that declaration.

Upvotes: 2

fredoverflow
fredoverflow

Reputation: 263350

consts on scalar return types are ignored, because there is no such thing as a scalar const rvalue.

That is, the following two declarations are exactly equivalent:

int foo();
const int foo();   // this particular const is ignored by the compiler

If you think about it, this makes sense: you cannot write foo() = 42; without the const, anyway.

Upvotes: 3

Let's look at an example:

class Foo {
private:
    int x;
public:
    Foo () : x (0) { }
    Foo (const Foo& other) { x = other.x; }
    void setX(const int newX) {
        x = newX;
    }
    const int getX() const {
        return x;
    }
    const Foo getFoo() const {
        return *this;
    }
};

Making getX() return a const int vs an int is silly. Any POD return-by-value type as const is pointless. Why would it matter? The only thing you can do to non-const POD variables you can't do to const ones is assignment...and the lvalue/rvalue distinction takes care of that for return types.

By contrast, when it's a return-by-value object, const can make a difference...because there may be operations you can perform on a non-const object that you can't on a const one. This can be undermined by a const copy constructor, though. Let's take getFoo() for example:

Foo foo;
/* foo.getFoo().setX(10); */ // ERROR!
// because f.getFoo() returns const Foo

Foo bar;
bar = foo.getFoo();
bar.setX(10); // no error

Making a by-value POD parameter type const protects against modifications of that parameter inside the function. So for instance, you can't assign a value to newX inside of setX.

All things being equal that would be all right...as it's a sort-of confusing practice to take an int parameter and change it inside the function; if you are debugging and want to know what the function was called with you have to go up the call stack to find out. But all things are not equal because it's more typing and screen clutter.

Similarly, making local POD variables const is more typing and clutter for less payoff. I only use it on globals.

So to me, I say const should be reserved for objects that have truly different semantic method dispatch (often indicated by lack of a const copy constructor)...or globals. Otherwise it's just getting in the way for minor or no benefit.

Upvotes: 1

PlasmaHH
PlasmaHH

Reputation: 16046

Yes.

It is always a good programming practice to express your intents in the programming language. You don't intend to change the variable, so make it const. Later on, when your compiler yells at you that you can't modify it when it is const, you will be happy that the compiler spotted some wrong ideas of your own design. This is not only true for const, but also for many other things, which is e.g. why in C++11 the override keyword was introduced.

Of course there are cases where const will not change anything, like when you return an int, but just like in other safety areas: better to have one const too much (that you might remove later because it wasn't really needed), than to have one too little (which will suddenly break stuff silently).

Upvotes: 7

Related Questions