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:
- Pass by Value
- Pass by Reference
- Pass by Pointer
- 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 originalx
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:
Method | Modifies Original | Copies Data | Use Case | Safe? |
---|---|---|---|---|
Pass by Value | ❌ No | ✅ Yes | Small data types, no need to modify | ✅ Very Safe |
Pass by Reference | ✅ Yes | ❌ No | Need to modify or avoid copying | ✅ Safe |
Pass by Pointer | ✅ Yes | ❌ No | Dynamic memory, optional args, C-style | ⚠️ Requires care |
Const Reference | ❌ No | ❌ No | Large 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:
Objective | Preferred Passing Style |
---|---|
Modify object | Pass by reference or pointer |
Read-only, large data | Pass by const reference |
Small object copy | Pass by value |