You can't have a race condition on a constant. It is easier to reason about a program when many of the objects cannot change their values.
Interfaces that promises "no change" of objects passed as arguments greatly increase readability.
Con.1: By default, make objects immutable
Immutable objects are easier to reason about, so make objects non-const only when there is a need to change their value.Prevents accidental or hard-to-notice change of value.
Exception - Function parameters passed by value are rarely mutated, but also rarely declared const.
To avoid confusion and lots of false positives, don't enforce this rule for function parameters.
Note that a function parameter is a local variable so changes to it are local.
Con.2: By default, make member functions const
A member function should be marked const unless it changes the object's observable state.
This gives a more precise statement of design intent, better readability, more errors caught by the compiler, and sometimes more optimization opportunities.
Note: It is not inherently bad to pass a pointer or reference to non-const, but that should be done only when the called function is supposed to modify the object.
A reader of code must assume that a function that takes a "plain" T* or T& will modify the object referred to.
If it doesn't now, it might do so later without forcing recompilation.
Note: There are code/libraries that offer functions that declare a T* even though those functions do not modify that T.
This is a problem for people modernizing code.
You can update the library to be const-correct; preferred long-term solution "cast away const";best avoided provide a wrapper function
// examplevoidf(int* p);// old code: f() does not modify `*p`voidf(constint* p){f(const_cast<int*>(p));}// wrapper
Note that this wrapper solution is a patch that should be used only when the declaration of f() cannot be modified, e.g. because it is in a library that you cannot modify.
Note: A const member function can modify the value of an object that is mutableor accessed through a pointer member.
A common use is to maintain a cache rather than repeatedly do a complicated computation.
For example, here is a Date that caches (memoizes) its string representation to simplify repeated uses:
classDate{public:// ...const string&string_ref()const{if(string_val =="")compute_string_rep();return string_val;}// ...private:voidcompute_string_rep()const;// compute string representation and place it in string_valmutable string string_val;// ...};
Another way of saying this is that constness is not transitive. It is possible for a const member function to change the value of mutable members and the value of objects accessed through non-const pointers.
It is the job of the class to ensure such mutation is done only when it makes sense according to the semantics (invariants) it offers to its users.
Con.3: By default, pass pointers and references to consts
To avoid a called function unexpectedly changing the value.
It's far easier to reason about programs when called functions don't modify state.
voidf(char* p);// does f modify *p? (assume it does)voidg(constchar* p);// g does not modify *p
Note: It is not inherently bad to pass a pointer or reference to non-const, but that should be done only when the called function is supposed to modify the object.
Note: Do not cast away const.
Con.4: Use const to define objects with values that do not change after construction
Prevent surprises from unexpectedly changed object values.
//Examplevoidf(){int x =7;constint y =9;for(;;){// ...}// ...}
As x is not const, we must assume that it is modified somewhere in the loop.
Con.5: Use constexpr for values that can be computed at compile time
Better performance, better compile-time checking, guaranteed compile-time evaluation, no possibility of race conditions.
double x =f(2);// possible run-time evaluationconstdouble y =f(2);// possible run-time evaluationconstexprdouble z =f(2);// error unless f(2) can be evaluated at compile time