Con: Constants and Immutability
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.
Constant rule summary:
- Con.1: By default, make objects immutable
- Con.2: By default, make member functions
const
- Con.3: By default, pass pointers and references to
const
s - Con.4: Use
const
to define objects with values that do not change after construction - Con.5: Use
constexpr
for values that can be computed at compile time
Con.1: By default, make objects immutable
Reason
Immutable objects are easier to reason about, so make object non-const
only when there is a need to change their value.
Prevents accidental or hard-to-notice change of value.
Example
for (const string& s : c) cout << s << '\n'; // just reading: const
for (string& s : c) cout << s << '\n'; // BAD: just reading
for (string& s: c) cin>>s; // needs to write: non-const
Exception
Function arguments are rarely mutated, but also rarely declared const. To avoid confusion and lots of false positives, don't enforce this rule for function arguments.
void f(const char*const p); // pedantic
void g(const int i); // pedantic
Note that function parameter is a local variable so changes to it are local.
Enforcement
- Flag non-const variables that are not modified (except for parameters to avoid many false positives)
Con.2: By default, make member functions const
Reason
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.
Example; bad
class Point {
int x, y;
public:
int getx() { return x; } // BAD, should be const as it doesn't modify the object's state
// ...
};
void f(const Point& pt) {
int x = pt.getx(); // ERROR, doesn't compile because getx was not marked const
}
Note
Enforcement
- Flag a member function that is not marked
const
, but that does not perform a non-const
operation on any member variable.
Con.3: By default, pass pointers and references to const
s
Reason
To avoid a called function unexpectedly changing the value. It's far easier to reason about programs when called functions don't modify state.
Example
void f(char* p); // does f modify *p? (assume it does)
void g(const char* 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
Enforcement
- flag function that does not modify an object passed by pointer or reference to non-cost
- flag a function that (using a cast) modifies an object passed by pointer or reference to const
Con.4: Use const
to define objects with values that do not change after construction
Reason
Prevent surprises from unexpectedly changed object values.
Example
void f()
{
int x = 7;
const int y = 9;
for (;;) {
// ...
}
// ...
}
As x
is not const, we must assume that it is modified somewhere in the loop.
Enforcement
- Flag unmodified non-const variables.
Con.5: Use constexpr
for values that can be computed at compile time
Reason
Better performance, better compile-time checking, guaranteed compile-time evaluation, no possibility of race conditions.
Example
double x = f(2); // possible run-time evaluation
const double x = f(2); // possible run-time evaluation
constexpr double y = f(2); // error unless f(2) can be evaluated at compile time
Note
See F.4.
Enforcement
- Flag
const
definitions with constant expression initializers.