C++ PROGRAMMING · 12 MIN STUDY

Passing arguments: value, reference, and pointer

Choose how a C++ function receives an argument: as an independent value, an alias to an existing object, or an address that may be absent.

Prerequisites and outcomes

Prerequisites: variables and functions. This lesson introduces the small amount of pointer and reference syntax needed for parameter choices. A reference is an alias for an existing object; a pointer stores an address and may be null. Their full treatment comes later in Lessons 37 and 38. After this lesson, you should be able to predict whether a function can change its caller's object, decide whether an argument may be absent, and explain the cost and safety trade-offs.

Three different contracts

Parameter formMeaningCan change caller's object?Can be null?
T valueFunction receives its own parameter objectNo, not through this parameterNo
T& valueAlias to an existing objectYesNo
const T& valueRead-only aliasNot through this referenceNo
T* valueAddress of an object, or nullYes, if valid and non-constYes

A value parameter is a separate parameter object. If the copied value is itself a pointer, the pointer value is copied, but the function may still modify the pointed-to object through a valid non-const pointer.

Pass by value: a separate parameter

#include <iostream>

void add_one(int number) {
    ++number;
    std::cout << "Inside function: " << number << '\n';
}

int main() {
    int score = 10;
    add_one(score);
    std::cout << "In main: " << score << '\n';
}
Expected output Inside function: 11 In main: 10

For an int, the value is copied into the parameter. Incrementing number changes that parameter, not score. For a large object, copying may be expensive; for small scalar types it is usually the clearest choice.

Pass by reference: an alias

#include <iostream>

void add_one(int& number) {
    ++number;
}

int main() {
    int score = 10;
    add_one(score);
    std::cout << score << '\n';
}
Expected output 11

The parameter is another name for the caller's object. A non-const reference communicates that the function may modify it. Use this when modification is part of the function's contract.

Pass by const reference: read without copying

#include <iostream>
#include <string>

void print_name(const std::string& name) {
    std::cout << name << '\n';
}

int main() {
    const std::string name{"Ada"};
    print_name(name);
}
Expected output Ada

This avoids copying a potentially large string and prevents the function from modifying it through this parameter. The caller must still keep the object alive for the call.

Pass by pointer: optional object

#include <iostream>

void reset_if_present(int* value) {
    if (value != nullptr) {
        *value = 0;
    }
}

int main() {
    int points = 9;
    reset_if_present(&points);
    reset_if_present(nullptr);
    std::cout << points << '\n';
}
Expected output 0

A pointer is useful when absence is meaningful or when working with APIs that use pointer semantics. Always check for null before dereferencing unless the function contract explicitly requires a valid pointer.

Worked trace: one copy and one alias

Complete C++17 program
#include <iostream>

void update(int value, int& reference) {
    value += 3;       // changes only the local copy
    reference += 5;   // changes the caller's object
    std::cout << "Local copy: " << value << '\n';
}

int main() {
    int score{4};
    update(score, score);
    std::cout << "Caller object: " << score << '\n';
}
Expected output Local copy: 7 Caller object: 9

Both parameters are initialized from score at the call. value receives a separate copy of 4, while reference becomes an alias for score. Adding 3 changes only the local copy to 7; adding 5 through the reference changes the caller's score from 4 to 9. The two output lines make the independent parameter object and the alias observable.

Common mistakes

  • Returning a reference or pointer to a local variable: the local object dies when the function returns.
  • Using a non-const reference when the function should only read: this makes the contract unnecessarily broad.
  • Dereferencing a nullable pointer without checking it.
  • Assuming const T& makes the original object globally immutable; it only restricts access through that reference.

Further reading: for a deeper treatment, continue later with Pointers (Lesson 37) and References (Lesson 38).

Practice

  1. Which parameter should a read-only function use for a large string?
  2. Which form communicates that no object may be absent?
  3. What happens if a function increments an int passed by value?
Show reasoned answers
  1. const std::string& avoids a copy and prevents modification through the parameter.
  2. A reference, if the function requires an existing object.
  3. Only the local parameter changes; the caller's integer remains unchanged.

Summary and next step

Choose parameter types as part of the function's contract: value for an independent copy, const reference for read-only access to an existing object, non-const reference for intentional modification, and pointer when optionality or pointer semantics matter. Next, study scope, lifetime, and storage duration.

References