CLOSE

In C++, how you pass arguments to a function determines how the function interacts with them—whether it modifies the original values, copies them, or simply accesses them read-only. There are four main strategies:

  1. Pass by Value
  2. Pass by Reference
  3. Pass by Pointer
  4. Pass by Constant Reference

1️⃣ Pass by Value

Definition:

The function receives a copy of the argument. Changes to this copy do not affect the original variable.

Example:

void increment(int num) {
    num++;  // This changes only the local copy of num
}

int main() {
    int x = 10;
    increment(x);  // x is passed by value
    // x is still 10 because the original value is not modified
}

Characteristics:

  • Effect: The function works with a copy of x. The original x remains unchanged.
  • Use Case: When you don’t need to modify the original argument and just need a local copy to work with.
  • Overhead: For small data types like int, char, etc., passing by value is efficient, but for larger objects like complex data structures, it might be costly in terms of performance because of the copying process.
+----------------+   +----------------+
| Original Value |   | Function's Copy|
|     x = 10     |   |     x = 10     |
+----------------+   +----------------+

Passing User-Defined Type:

#include <iostream>

struct Point {
    int x;
    int y;
};

void modifyPoint(Point p) {
    p.x = 20;  // This modifies only the local copy of 'p'
    p.y = 30;
    std::cout << "Inside function (Pass by Value): (" << p.x << ", " << p.y << ")" << std::endl;
}

int main() {
    Point p1 = {10, 15};
    
    modifyPoint(p1);  // p1 is passed by value, so original p1 is not changed
    std::cout << "After function call: (" << p1.x << ", " << p1.y << ")" << std::endl;

    return 0;
}

 Output:

Inside function (Pass by Value): (20, 30)
After function call: (10, 15)

Note:

Avoid using pass by value for large structs or classes—use const reference instead to avoid expensive copying.

2️⃣ Pass by Reference

Definition:

When passing by reference, the function gets an alias (or reference) to the original variable. Any changes made to the reference inside the function will directly affect the original variable.

Example:

void increment(int &num) {
    num++;  // This changes the original num
}

int main() {
    int x = 10;
    increment(x);  // x is passed by reference
    // x is now 11 because the original value is modified
}

Characteristics:

  • Effect: The function operates directly on x, modifying the original value.
  • Use Case: When you want the function to modify the original variable.
  • Overhead: More efficient than pass by value for larger objects, since no copying is done, only a reference to the original object is passed.
+----------------+  
| Original Value |  
|     x = 10     |  
+----------------+
   ^
   |
 Function's Reference to x

Passing User-Defined Type:

#include <iostream>

struct Point {
    int x;
    int y;
};

void modifyPoint(Point &p) {
    p.x = 20;  // Modifies the original p1
    p.y = 30;
    std::cout << "Inside function (Pass by Reference): (" << p.x << ", " << p.y << ")" << std::endl;
}

int main() {
    Point p1 = {10, 15};
    
    modifyPoint(p1);  // p1 is passed by reference, so original p1 is modified
    std::cout << "After function call: (" << p1.x << ", " << p1.y << ")" << std::endl;

    return 0;
}

Output:

Inside function (Pass by Reference): (20, 30)
After function call: (20, 30)

3️⃣ Pass by Pointer

Definition:

Passing by pointer is similar to passing by reference, but instead of passing an alias, you pass the address of the variable. Inside the function, you dereference the pointer to access or modify the variable.

Example:

void increment(int *num) {
    (*num)++;  // Dereference the pointer and increment the original num
}

int main() {
    int x = 10;
    increment(&x);  // Pass the address of x
    // x is now 11 because the original value is modified
}

Characteristics:

  • Effect: The function operates on the variable through its pointer, allowing direct modification of the original value.
  • Use Case: Useful when working with dynamic memory or when you need to pass nullptr to indicate the absence of a value.
  • Overhead: No copying of the object, but you must deal with pointers, which adds complexity.
  • Safety: Requires careful handling since working with pointers can lead to bugs if not used correctly, such as dereferencing null or invalid pointers.
+----------------+
| Original Value |
|     x = 10     |
+----------------+
      ^
      |
Function has x's Address (&x)

Passing User-Defined Type:

#include <iostream>

struct Point {
    int x;
    int y;
};

void modifyPoint(Point *p) {
    p->x = 20;  // Dereference and modify the original p1
    p->y = 30;
    std::cout << "Inside function (Pass by Pointer): (" << p->x << ", " << p->y << ")" << std::endl;
}

int main() {
    Point p1 = {10, 15};
    
    modifyPoint(&p1);  // Pass the address of p1
    std::cout << "After function call: (" << p1.x << ", " << p1.y << ")" << std::endl;

    return 0;
}

Output:

Inside function (Pass by Pointer): (20, 30)
After function call: (20, 30)

Best Practice:

Always check for nullptr before using a pointer.

void safeIncrement(int* num) {
    if (num != nullptr) (*num)++;
}

4️⃣ Pass by Constant Reference

Definition:

Passing by constant reference is used when you want to avoid copying the argument but still prevent the function from modifying the original argument. This is especially useful for large objects where copying would be inefficient, but you don't want the function to alter the data.

Example:
void print(const int &num) {
    // num cannot be modified because it's a constant reference
    std::cout << num << std::endl;
}

int main() {
    int x = 10;
    print(x);  // x is passed by constant reference
    // x remains unchanged
}

Characteristics:

  • Effect: The function can access the original value but cannot modify it.
  • Use Case: Best for read-only access to large objects where copying is costly but you don’t want the function to alter the original value.
  • Overhead: Similar to pass by reference, but safer since the original data cannot be modified.
+----------------+  
| Original Value |  
|     x = 10     |  
+----------------+
   ^
   |
 Function's Read-only Reference to x

Comparison of the Methods:

MethodModifies OriginalCopies DataUse CaseSafe?
Pass by Value❌ No✅ YesSmall data types, no need to modify✅ Very Safe
Pass by Reference✅ Yes❌ NoNeed to modify or avoid copying✅ Safe
Pass by Pointer✅ Yes❌ NoDynamic memory, optional args, C-style⚠️ Requires care
Const Reference❌ No❌ NoLarge objects, read-only access✅ Very Safe

Additional Notes

Combining with Default Arguments:

You can use default values with all methods:

void greet(const std::string &name = "Guest") {
    std::cout << "Hello, " << name << "!" << std::endl;
}

Const-Correctness Best Practice:

Use const references wherever mutation is not required.

void printDetails(const MyObject &obj); // Good
void printDetails(MyObject obj);        // Bad for large object

User-Defined Types Best Recommendation:

ObjectivePreferred Passing Style
Modify objectPass by reference or pointer
Read-only, large dataPass by const reference
Small object copyPass by value