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

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

Do not cast away const.

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 consts

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

Do not cast away const.

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.