About six years ago, a software engineer named Harri Porten wrote this article, asking the question, "When should a member function have a const qualifier and when shouldn't it?" I found it to be the best write-up I could find of the issue, which I've been wrestling with more recently and which I think is not well covered in most discussions I've found on const correctness. Since a software information-sharing site as powerful as SO didn't exist back then, I'd like to resurrect the question here.
views:
199answers:
6when it doesn't modify the object.
It simply makes this
to have type const myclass*
. This guarantees a calling function that the object won't change. Allowing some optimizations to the compiler, and easier for the programmer to know if he can call it without side effects (at least effects to the object).
The article seems to cover a lot of basic ground, but the author still has a question about const and non-const overloads of functions returning pointers. Last line of the article is:
Many will probably answer "It depends." but I'd like to ask "It depends on what?"
To be absolutely precise, it depends whether the state of the A object pointee is logically part of the state of this
object.
For an example where it is, vector<int>::operator[]
returns a reference to an int. The int referand is "part of" the vector, although it isn't actually a data member. So the const-overload idiom applies: change an element and you've changed the vector.
For an example where it isn't, consider shared_ptr
. This has the member function T * operator->() const;
, because it makes logical sense to have a const smart pointer to a non-const object. The referand is not part of the smart pointer: modifying it does not change the smart pointer. So the question of whether you can "reseat" a smart pointer to refer to a different object is independent of whether or not the referand is const.
I don't think I can provide any complete guidelines to let you decide whether the pointee is logically part of the object or not. However, if modifying the pointee changes the return values or other behaviour of any member functions of this
, and especially if the pointee participates in operator==
, then chances are it is logically part of this
object.
I would err on the side of assuming it is part (and provide overloads). Then if a situation arose where the compiler complains that I'm trying to modify the A object returned from a const object, I'd consider whether I really should be doing that or not, and if so change the design so that only the pointer-to-A is conceptually part of the object's state, not the A itself. This of course requires ensuring that modifying the A doesn't do anything that breaks the expected behaviour of this
const object.
If you're publishing the interface you may have to figure this out in advance, but in practice going back from the const overloads to the const-function-returning-non-const-pointer is unlikely to break client code. Anyway, by the time you publish an interface you hopefully have used it a bit, and probably got a feel for what the state of your object really includes.
Btw, I also try to err on the side of not providing pointer/reference accessors, especially modifiable ones. That's really a separate issue (Law of Demeter and all that), but the more times you can replace:
A *getA();
const A *getA() const;
with:
A getA() const; // or const A &getA() const; to avoid a copy
void setA(const A &a);
The less times you have to worry about the issue. Of course the latter has its own limitations.
One interesting rule of thumb I found while researching this came from here:
A good rule of thumb for LogicalConst is as follows: If an operation preserves LogicalConstness, then if the old state and the new state are compared with the EqualityOperator, the result should be true. In other words, the EqualityOperator should reflect the logical state of the object.
I personally use a very simple Rule Of Thumb:
If the observable state of an object does not change when calling a given method, this method ought to be const
.
In general it is similar to the rule mentioned by SCFrench
about Equality Comparison, except that most of my classes cannot be compared.
I would like to push the debate one step further though:
When requiring an argument, a function ought to take it by const
handle (or copy) if the argument is left unchanged (for an external observer)
It is slightly more general, since after all the method of a class is nothing else than a free-standing function accepting an instance of the class as a first argument:
class Foo { void bar() const; };
is equivalent to:
class Foo { friend void bar(const Foo& self); }; // ALA Python
Here are some good articles:
Herb Sutter's GotW #6
Herb Sutter & const for optimizations
More advice on const correctness
From Wikipedia
I use const
method qualifiers when the method does not alter the class' data members or its common intent is not to modify the data members. One example involves RAII for a getter method that may have to initialize a data members (such as retrieve from a database). In this example, the method only modifies the data member(s) once during initialization; all other times it is constant.
I'm allowing the compiler to catch const errors during compile time rather than me catching them during run-time (or a User).