CppParameters & Pass by Value/Reference

Parameters & Pass by Value/Reference

How you pass arguments into a function has real consequences for performance and correctness in C++. Unlike some higher-level languages, C++ gives you explicit control over whether an argument is copied, referenced, or pointed to.

Pass by Value
By default, C++ passes arguments by value: the function receives its own independent copy of the argument. Modifying the parameter inside the function has no effect on the caller's original variable.

CPP
#include <iostream>
using namespace std;

void increment(int x) {
    x = x + 1; // only modifies the local copy
}

int main() {
    int a = 5;
    increment(a);
    cout << a << endl; // Output: 5 - unchanged!
    return 0;
}
Warning
Passing by value copies the entire argument. For simple types like int or double this is cheap, but for large objects — a std::vector with a million elements, a large std::string, a custom class — the copy can be expensive and wasteful, especially when the function only needs to read the value.
Pass by Reference (&)
A reference parameter is an alias for the caller's actual variable — no copy is made, and changes inside the function are visible to the caller. Use & after the type to declare a reference parameter.

CPP
#include <iostream>
using namespace std;

void increment(int& x) {
    x = x + 1; // modifies the caller's actual variable
}

int main() {
    int a = 5;
    increment(a);
    cout << a << endl; // Output: 6 - changed!
    return 0;
}
Pass by const Reference (const &)
const reference parameters give you the best of both worlds: no copy is made (fast, like a reference), but the function cannot modify the original (safe, like pass-by-value). This is the idiomatic way to pass large objects — strings, vectors, custom classes — when the function only needs to read them.

CPP
#include <iostream>
#include <string>
using namespace std;

// No copy of the string is made, and it cannot be modified here.
void printLength(const string& s) {
    cout << s << " has " << s.size() << " characters" << endl;
    // s += "!";  // would not compile - s is const
}

int main() {
    string message = "Hello, world!";
    printLength(message);
    return 0;
}
Tip
As a rule of thumb: pass small, cheap types (int, double, char, bool) by value, and pass larger objects by const reference unless you specifically need to modify the caller's copy.
Pass by Pointer
A pointer parameter receives the address of a variable. Like a reference, it lets the function modify the caller's data, but unlike a reference, a pointer can be nullptr and must be explicitly dereferenced with * to access the value it points to.

CPP
#include <iostream>
using namespace std;

void increment(int* x) {
    if (x != nullptr) {
        *x = *x + 1; // dereference to modify the caller's variable
    }
}

int main() {
    int a = 5;
    increment(&a); // pass the address of a
    cout << a << endl; // Output: 6

    increment(nullptr); // safe - the function checks for null
    return 0;
}
Comparison

Passing method

Copy made?

Can modify caller?

Can be null?

By value (T)

Yes

No

No

By reference (T&)

No

Yes

No

By const reference (const T&)

No

No

No

By pointer (T*)

No

Yes

Yes

Key Points
  • Pass by value copies the argument; changes inside the function do not affect the caller.

  • Pass by reference (T&) avoids the copy and lets the function modify the caller's variable.

  • Pass by const reference (const T&) avoids the copy without allowing modification - ideal for large read-only arguments.

  • Pass by pointer (T*) allows modification and can represent "no value" via nullptr, but requires dereferencing.

  • Prefer const reference over pass-by-value for anything larger than a small primitive type.