All Courses
Functions

Function Parameters in C++


Introduction

Function parameters define how data flows into functions. C++ offers multiple parameter-passing mechanisms, each with distinct performance and safety characteristics.

Core Question: Should the function read, modify, or own the data?

Intent Recommended Approach
Read only (small type) Pass by value
Read only (large type) const T&
Modify caller's data T& (reference)
Optional/nullable T* (pointer)
Transfer ownership T&& or smart pointer

Parameter Passing Methods

Overview Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  PARAMETER PASSING IN C++                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚  BY VALUE   β”‚  β”‚ BY REFERENCEβ”‚  β”‚  BY POINTER β”‚         β”‚
β”‚  β”‚   (copy)    β”‚  β”‚  (alias)    β”‚  β”‚  (address)  β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚         β”‚                β”‚                β”‚                 β”‚
β”‚    void f(int x)    void f(int &x)   void f(int *x)        β”‚
β”‚         β”‚                β”‚                β”‚                 β”‚
β”‚   Creates copy     Same memory      Holds address          β”‚
β”‚   Changes local    Changes original Can be nullptr         β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Pass by Value

The function receives a copy of the argument. Changes inside the function don't affect the original.

Syntax

void func(int x);      // x is a copy
void func(string s);   // s is a copy (expensive for large strings!)

When to Use

  • Small, cheap-to-copy types (int, double, char, bool)
  • When function needs its own copy to modify
  • When you want to guarantee caller's data is unchanged

Example

void doubleValue(int x) {
    x = x * 2;  // Only affects local copy
    cout << "Inside: " << x << endl;
}

int main() {
    int num = 5;
    doubleValue(num);
    cout << "Outside: " << num << endl;  // Still 5!
}

Performance Guide

// INEFFICIENT - copies entire vector!
void process(vector<int> data) { ... }

// EFFICIENT - no copy
void process(const vector<int>& data) { ... }

Pass by Reference

The function receives an alias to the original variable. Changes affect the caller's data.

Syntax

void func(int& x);         // Reference to int
void func(string& s);      // Reference to string
void func(vector<int>& v); // Reference to vector

When to Use

  • Function needs to modify caller's variable
  • Avoid copying large objects
  • Returning multiple values (out parameters)

Example: Swap Function

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);
    cout << x << ", " << y << endl;  // 20, 10
}

Example: Multiple Outputs

void minMax(const vector<int>& nums, int& outMin, int& outMax) {
    if (nums.empty()) return;
    outMin = outMax = nums[0];
    for (int n : nums) {
        if (n < outMin) outMin = n;
        if (n > outMax) outMax = n;
    }
}

int main() {
    vector<int> data = {3, 1, 4, 1, 5, 9};
    int min, max;
    minMax(data, min, max);
    cout << "Min: " << min << ", Max: " << max << endl;
}

Pass by Pointer

The function receives the memory address of the variable. Similar to reference but can be null.

Syntax

void func(int* ptr);        // Pointer to int
void func(const int* ptr);  // Pointer to const int (read-only)
void func(int* const ptr);  // Const pointer (can't reassign)

When to Use

  • Parameter is optional (can pass nullptr)
  • Working with arrays
  • Interfacing with C code
  • Dynamic memory management

Example: Optional Output

bool divide(int a, int b, double* result) {
    if (b == 0) return false;
    if (result) {  // Check for nullptr
        *result = static_cast<double>(a) / b;
    }
    return true;
}

int main() {
    double res;
    if (divide(10, 3, &res)) {
        cout << "Result: " << res << endl;
    }

    // Just check if divisible, don't need result
    if (divide(10, 0, nullptr)) {
        cout << "Divisible" << endl;
    }
}

Pointer vs Reference Comparison

Feature Reference Pointer
Can be null No Yes
Can be reassigned No Yes
Syntax Cleaner Requires * and &
Must be initialized Yes No

Const Parameters

The const keyword prevents modification, enabling optimization and catching errors.

Const Reference (Most Common)

// Read-only access, no copy
void print(const string& s) {
    cout << s << endl;
    // s = "new";  // ERROR: can't modify const
}

void process(const vector<int>& data) {
    for (int n : data) {
        cout << n << " ";
    }
    // data.push_back(1);  // ERROR
}

Const Pointer Variations

void f1(const int* p);   // Can't modify *p, can reassign p
void f2(int* const p);   // Can modify *p, can't reassign p
void f3(const int* const p);  // Can't modify either

Visual Guide

const int* ptr        β†’  "pointer to const int"    β†’  *ptr is read-only
int* const ptr        β†’  "const pointer to int"    β†’  ptr is read-only
const int* const ptr  β†’  both are read-only

Default Arguments

Provide default values for parameters that are optional.

Syntax Rules

// Defaults must be rightmost parameters
void greet(string name, string greeting = "Hello", int times = 1);

// INVALID: default before non-default
// void greet(string greeting = "Hello", string name);  // ERROR

Example

void printMessage(const string& msg,
                  int count = 1,
                  const string& prefix = ">> ") {
    for (int i = 0; i < count; i++) {
        cout << prefix << msg << endl;
    }
}

int main() {
    printMessage("Hello");              // Uses all defaults
    printMessage("Hello", 3);           // Custom count
    printMessage("Hello", 2, "!! ");    // All custom
}

Important Rules

  1. Defaults in declaration (header), not definition
  2. Right-to-left ordering required
  3. Once a default is used, all following must have defaults
// header.h
void connect(string host, int port = 80, bool secure = false);

// source.cpp
void connect(string host, int port, bool secure) {
    // Don't repeat defaults here!
}

Array Parameters

Arrays decay to pointers when passed to functions.

Array Decay

void process(int arr[]);     // Actually: void process(int* arr)
void process(int arr[10]);   // Still just a pointer! Size ignored.
void process(int* arr);      // Most honest declaration

Passing Array with Size

void printArray(const int* arr, size_t size) {
    for (size_t i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int nums[] = {1, 2, 3, 4, 5};
    printArray(nums, 5);
}

Modern Alternative: std::span (C++20)

#include <span>
void process(std::span<int> arr) {
    for (int n : arr) {
        cout << n << " ";
    }
}

Reference to Array (Preserves Size)

template<size_t N>
void processFixed(int (&arr)[N]) {
    cout << "Array size: " << N << endl;
    for (int i = 0; i < N; i++) {
        cout << arr[i] << " ";
    }
}

Recommended Conventions

Decision Flowchart

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ Choosing Parameter  β”‚
                    β”‚       Type          β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ Need to modify      β”‚
                    β”‚ caller's data?      β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          YES  β”‚  NO
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚                     β”‚
              β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
              β”‚ Can be    β”‚        β”‚ Small type? β”‚
              β”‚ nullptr?  β”‚        β”‚ (int, etc.) β”‚
              β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
               YES  β”‚  NO            YES  β”‚  NO
              β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
              β”‚            β”‚       β”‚             β”‚
          β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”
          β”‚  T*   β”‚  β”‚   T&     β”‚ β”‚ T     β”‚ β”‚const T& β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚(value)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β””β”€β”€β”€β”€β”€β”€β”€β”˜

Guidelines Summary

Scenario Use
Small built-in type, read-only int x
Small built-in type, modify int& x
Large object, read-only const T& x
Large object, modify T& x
Optional parameter T* x
Ownership transfer unique_ptr<T> or T&&

Code Examples

// βœ“ Good
void process(const vector<int>& data);
void update(string& name);
bool find(const map<int,int>& m, int key, int* outValue);

// βœ— Avoid
void process(vector<int> data);  // Unnecessary copy
void update(string* name);       // Use reference if never null

Cheat Sheet Card

// VALUE - copy, safe, may be slow for large types
void f(int x);

// REFERENCE - alias, modifies original
void f(int& x);

// CONST REFERENCE - alias, read-only, efficient
void f(const string& x);

// POINTER - can be null, explicit dereferencing
void f(int* x);

// CONST POINTER - read-only access
void f(const int* x);

// DEFAULT - optional parameters
void f(int x, int y = 0);

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples