Docs
README
Function Parameters in C++
Table of Contents
- •Introduction
- •Parameter Passing Methods
- •Pass by Value
- •Pass by Reference
- •Pass by Pointer
- •Const Parameters
- •Default Arguments
- •Array Parameters
- •Best Practices
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 Note
// 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
- •Defaults in declaration (header), not definition
- •Right-to-left ordering required
- •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] << " ";
}
}
Best Practices
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
Quick Reference 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