C++ Pass by Value vs Const Reference Explained

In C++, passing an argument by value versus passing by const reference is primarily a distinction between ownership and observation. Passing by value creates an independent, isolated copy of the argument for the function to consume or modify without affecting the caller. In contrast, passing by const reference provides an immutable alias to the original object, signaling that the function only intends to observe the data without taking ownership or incurring the performance cost of a copy.

Semantic Ownership and Independence

When a function accepts a parameter by value (void func(T x)), it demands its own independent instance of T. Semantically, this implies:

  • Value Semantics: The function receives a complete copy of the data. Modifications made to x inside the function have zero effect on the caller's original object.
  • Ownership Transfer or Sinks: Passing by value is often used when the function intends to consume, store, or move the argument (such as initializing a member variable in a constructor via std::move).
  • Guaranteed Lifetime: The parameter's lifetime is bound directly to the local scope of the function, eliminating concerns about the caller destroying the object prematurely.

Observation and Lifetime Semantics

When a function accepts a parameter by const reference (void func(const T& x)), it requests read-only access to an existing instance. Semantically, this implies:

  • Observation Only: The caller retains full ownership. The function promises not to mutate the object through that reference and merely inspects its state.
  • Non-Owning Reference: The function does not own the object. Storing a const reference beyond the function's execution is hazardous because it introduces the risk of dangling references if the caller's object falls out of scope.
  • Temporary Binding: A const reference can bind to temporary objects (rvalues). The compiler extends the temporary's lifetime to match the duration of the function call.

Aliasing and Side Effects

A subtle semantic difference involves pointer or reference aliasing.

With pass by value, the local instance is completely isolated. Even if global state or another parameter points to the caller's original object, mutating that original object will not alter the local copy.

With pass by const reference, x is an alias for the actual object in memory. While x itself cannot be modified directly, the underlying data might change during function execution if another non-const pointer or reference to the same object modifies it concurrently or through a reentrant function call.

Practical Guidelines

  • Pass by Value: Use for cheap-to-copy types (such as fundamental types like int, double, or small structs like std::pair<int, int>) and for parameters that will be moved or copied internally anyway.
  • Pass by Const Reference: Use for non-trivial types (such as std::string, std::vector, or complex custom classes) where the function only needs to read the data, preventing the computational and memory overhead of unnecessary object duplication.