Skip to content

Use const correctness to improve code safety and clarity

Const correctness in C++ refers to the practice of using the const keyword to ensure that variables, objects, and member functions do not unintentionally modify data, improving code safety and clarity.

Applying const is an expression of intent as much as it is an instruction to the compiler to verify that you conform to the expressed intent. Omitting const is therefore just as much an expression of intent: This variable will be modified.

Returning a const value does not make sense, the client is able to copy the value into a non-const object anyway. It also disables move semantics, which is a constraint that has no benefits.

const Widget getWidget() { /* ... */ }
// ...
auto widget = getWidget() // Creates a mutable copy
widget.change(); // Able to change

Prefer returning by value over returning by const reference for value objects

Section titled “Prefer returning by value over returning by const reference for value objects”

When returning a const reference, you risk creating a dangling dangling reference:

When returning a const reference, it also increases cognitive load on the reader, which can be avoided by using the Value Semantics . * F.15: Prefer simple and conventional ways of passing information

Using value semantics is more clear and appropriate for Value Objects, which represent data without identity.

  • Return by-reference suggests shared/mutable state and that the specific object instance is important.
  • Return by value, value semantics, suggest that the identity (specific instance) is not important.

Return by const reference can be used as optimization if it would be genuinely expensive to copy the object, but be aware of the risks.

class Widget {
Point position_;
std::string name_;
std::vector<LargeData> cache_;
public:
// ...
// AVOID - returning references to value objects
const Point& get_position() const;
const std::string& get_name() const;
// PREFER - return by value for value objects
Point get_position() const;
std::string get_name() const;
// OK - reference appropriate to avoid expensive copy
const std::vector<LargeData>& get_cache() const;
}
  • Function arguments:
    • Pass cheap things by value: int.
    • Pass expensive things by const reference: const Widget&.
    • Pass out-parameters by pointer: Widget*.
    • Anything else: Wrong according to Arthur.
  • Data members: never const
  • Return types: never const
    • Preserve invariants with private, not with const
  • Part I: functions and local variables
    • Introduction to const. Recommends in particular to use const functions and const local variables.
  • Part 2: member variables
    • Be aware that const member variables are very restrictive
      • Const member variables make an object not assignable
      • That const member variables disable the move semantics of the object
  • Part 3: return types
    • Return by const value can prevent optimizations (RVO), and it does not make sense to do so.
    • Return by const reference risks dangling references, don’t create a habit out of it.
    • Return by const pointer
      • Be aware of const pointer vs pointer-to-const object
      • Return pointer-to-const makes sense, the pointed to data cannot be modified
      • Return const pointer to mutable object, does not make sense (in the same way as return by const value)
  • Part 4: parameters
    • Do not take small primitives by const reference, it is inefficient
    • Take by const value is fine and recommended by Dargo.
    • For objects take by const reference or possibly by value (copy). Never by const value.