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