In C++ programming, function parameter passing seems like a basic operation, yet it hides many key details that affect code performance and safety. Beginners often get confused about why “pass by value cannot modify the original variable,” and even experienced developers may stumble over the choice between “pointers vs references.” The core of this issue lies in the underlying logical differences among the three mechanisms: pass by value, pass by pointer, and pass by reference.
Let’s clarify a commonality: regardless of the passing method, a “copy” occurs when calling a function—however, what is copied is either the “data itself” or the “address,” which is the most fundamental difference among the three methods.
For example, pass by value copies the complete data of the actual parameter, while pointer/reference passing copies the address (or the binding relationship of the address); especially in pointer passing, many people mistakenly believe that “the formal parameter and the actual parameter are the same pointer,” but in fact, the formal parameter is a copy of the actual parameter’s pointer, merely pointing to the same memory block (this is also key to understanding double pointers, which we will discuss later).
Part 1Pass by Value
1.1 What is Pass by Value?
Pass by value is the most basic parameter passing method. The core concept is “passing a complete copy of the actual parameter“—when you call a function that uses pass by value, the compiler creates an independent memory space for the formal parameter in the function’s stack frame, and then copies all the data of the actual parameter “intact” into this space.
For example:
We define <span><span>main</span></span> function with <span><span>int num = 5</span></span>, <span><span>num</span></span> will exist in the stack frame of the <span><span>main</span></span> function (assuming the address is <span><span>0x0012ff44</span></span>); when calling <span><span>increment(num)</span></span>, the compiler will allocate a new memory space for the formal parameter <span><span>x</span></span> in the stack frame of the <span><span>increment</span></span> function (for example, at address <span><span>0x0012ff00</span></span>), and then copy the value of <span><span>num</span></span> (5) into the memory of <span><span>x</span></span>.
This means that:
The operations on <span><span>x</span></span> inside the function and <span><span>num</span></span> in <span><span>main</span></span> are two completely independent memory spaces—even if you change <span><span>x</span></span> drastically, <span><span>num</span></span> will not be affected at all; once the function execution ends, the stack frame of <span><span>increment</span></span> will be destroyed, and the memory of <span><span>x</span></span> will also be released, leaving no side effects on the original variable.
Example:
#include <iostream>
using namespace std;
// Formal parameter x: created in increment's stack frame, is a copy of num
void increment(int x) {
x++; // Only modifies x's memory (0x0012ff00), unrelated to num's memory (0x0012ff44)
cout << "Value of x inside the function:" << x << ", address of x:" << &x << endl; // Outputs 6, address 0x0012ff00 (example)
}
int main() {
int num = 5; // num in main's stack frame, address assumed to be 0x0012ff44
cout << "Initial value of num outside the function:" << num << ", address of num:" << &num << endl; // Outputs 5, address 0x0012ff44 (example)
increment(num); // Copies num's value to x, not passing num's address
cout << "Final value of num outside the function:" << num << endl; // Outputs 5, num's memory remains unchanged
return 0;
}
1.2 The Underlying Principles of Pass by Value
- Memory Allocation: A new space is allocated on the stack to store the copy of the formal parameter when the function is called.
- Copy Overhead: There is almost no overhead for basic types (like
<span><span>int</span></span>), but for large objects (like<span><span>std::string</span></span>), it will trigger the constructor and destructor calls.
1.3 Three Key Characteristics of Pass by Value
1) Maximum safety, but copy overhead is a hard constraint
The “independence” of pass by value is its greatest advantage—no matter how the function operates on the formal parameter, it will not affect the original data, fully complying with the design principle of “no side effects functions,” making it particularly suitable for handling sensitive data (like user passwords, configuration parameters). However, the problem lies in the “copy overhead”:
- If the passed data is of basic types (like
<span><span>int</span></span>,<span><span>float</span></span>), the copy speed is extremely fast, and the overhead is negligible; - But if the passed data is large objects (like a
<span><span>std::vector</span></span>containing 1000 elements, or complex classes with multiple members), copying will require duplicating all member data— for example, a<span><span>vector<int> vec(1000, 1)</span></span>will need to copy 1000<span><span>int</span></span>(totaling 4000 bytes), and if this function is called frequently, the performance loss will be very noticeable.
2) Will trigger the copy constructor of the object
For custom class objects, pass by value not only copies member data but also explicitly calls the copy constructor (if the user has not defined it, the compiler will generate a default copy constructor). This is easily overlooked, for example:
class MyClass {
public:
MyClass() { cout << "Default constructor" << endl; }
// Copy constructor
MyClass(const MyClass& other) { cout << "Copy constructor" << endl; }
};
void func(MyClass obj) {} // Pass by value
int main() {
MyClass a; // Outputs "Default constructor"
func(a); // Outputs "Copy constructor" (copying a to obj)
return 0;
}
If <span><span>MyClass</span></span> has complex logic in its copy constructor (like deep copying dynamic memory), the overhead of pass by value will further increase.
3) The lifetime of the formal parameter is independent of the actual parameter
The formal parameter only exists during the execution of the function (located in the function’s stack frame), and will be automatically destroyed after the function ends (calling the destructor, if any), without any association with the lifetime of the actual parameter. This is safer than references/pointers, as there is no need to worry about “dangling references” or “wild pointers.”
1.4 Suitable Scenarios for Pass by Value
Scenario 1: Passing Basic Data Types
For example, <span><span>int</span></span>, <span><span>char</span></span>, <span><span>bool</span></span>, <span><span>double</span></span>, etc., where the copy overhead is small, and safety is prioritized. For example, to implement a function that “calculates the sum of two numbers”:
int add(int a, int b) { return a + b; } // Pass by value is most suitable
Scenario 2: Passing Small Structures/Objects
For example, structures with 2-3 members (like a coordinate represented by <span><span>Point</span></span>), or classes without complex members, where the copy overhead is acceptable. For example:
struct Point {
int x;
int y;
};
// Print coordinates, no need to modify the original Point, use pass by value
void printPoint(Point p) {
cout << "(" << p.x << "," << p.y << ")" << endl;
}
Scenario 3: Functions that do not need to modify the original data and need to ensure data safety
For example, validation functions for user input, logging functions, using pass by value can avoid accidental tampering with the original data.
Part 2Pass by Reference
2.1 What is Pass by Reference?
Reference (<span><span>&</span></span>) is essentially a “nickname” for a variable—it does not have its own independent memory space but is “bound” to the original variable, sharing the same memory address. The core of pass by reference is “passing the alias relationship” rather than the data itself.
Let’s clarify a common misconception: “References are syntactic sugar for pointers”—from the underlying implementation perspective, the compiler indeed treats references as “implicit pointers” (for example, in a 64-bit system, the underlying reference is also an 8-byte address), but there are strict syntactic restrictions:
- References must be immediately bound to a variable upon definition (unlike pointers, which can be “defined first, assigned later”);
- Once a reference is bound, it cannot point to another variable (pointers can change their target at any time);
- References cannot bind to
<span><span>nullptr</span></span>(pointers can point to null).
These restrictions make references safer than pointers while retaining the efficiency of “directly operating on original data.” For example, when calling <span><span>increment(num)</span></span>, the formal parameter <span><span>int& x</span></span> is an alias for <span><span>num</span></span>—the address of <span><span>x</span></span> is exactly the same as that of <span><span>num</span></span><code><span><span>, and operating on </span></span><code><span><span>x</span></span> is equivalent to operating on <span><span>num</span></span><span><span>.</span></span>
Example:
#include <iostream>
using namespace std;
// Formal parameter x: alias for num, sharing the same memory
void increment(int& x) {
x++; // Directly modifies x (i.e., num) memory, address is the same as num
cout << "Value of x inside the function:" << x << ", address of x:" << &x << endl; // Outputs 6, address same as num
}
int main() {
int num = 5; // Address of num assumed to be 0x0012ff44
cout << "Initial value of num outside the function:" << num << ", address of num:" << &num << endl; // Outputs 5, address 0x0012ff44
increment(num); // Binds x as an alias for num, no data copy
cout << "Final value of num outside the function:" << num << endl; // Outputs 6, original data modified
return 0;
}
2.2 The Underlying Principles of Pass by Reference
- Alias Mechanism: A reference is essentially an alias for a variable (the C++ standard requires that a reference must be bound to an existing object).
- Zero Overhead: No data copying is required, directly accessed via memory address.
2.3 Constant References vs Non-Constant References
// Constant reference: prohibits modification of actual parameter
void print(const std::string &s) {
std::cout << s << std::endl;
}
// Non-constant reference: allows modification of actual parameter
void modify(std::string &s) {
s += " modified";
}
Advantages of Constant References:
- Avoids unintentional data modification
- Supports binding to temporary objects (like
<span><span>print("hello")</span></span>)
2.4 Four Core Characteristics of Pass by Reference
1) Zero copy overhead, maximum efficiency
Pass by reference does not require copying any data, only establishing “alias binding” at compile time— even if a 1GB large object is passed, the overhead is merely “binding the address” (pointer operations at the underlying level), which is several orders of magnitude more efficient than pass by value. This is why passing <span><span>std::string</span></span>, <span><span>std::vector</span></span> and other large containers is almost always done using references.
2) Const references are “read-only protectors”
If we do not need to modify the original data, we must use <span><span>const T&</span></span> (constant reference)— it has two key functions:
- Prohibits the function from modifying the original data, ensuring safety (for example,
<span><span>void print(const vector<int>& vec)</span></span>, the function cannot change the elements of<span><span>vec</span></span>); - Allows binding to temporary variables (non-const references cannot do this).
For example:
// Non-const reference: cannot pass temporary variable
void func1(string& s) {}
// Const reference: can pass temporary variable
void func2(const string& s) {}
int main() {
// func1("hello"); // Compilation error: temporary variable cannot bind to non-const reference
func2("hello"); // Compilation passes: temporary variable "hello" can bind to const reference
return 0;
}
This is very commonly used in practice— for example, if the function parameter is
<span><span>const string&</span></span><span><span>, it can accept both variables and string literals, providing greater flexibility.</span></span><p><strong><span><span>3) The lifetime of a reference must be "less than or equal to" that of the original variable</span></span></strong></p><p><span><span>This is the most common pitfall in pass by reference— if the variable to which the reference is bound is destroyed, the reference becomes a "dangling reference," and accessing it will trigger undefined behavior (program crashes, garbage output, etc.). For example:</span></span></p><pre><code class="language-c">// Error example: returning a reference to a local variable
int& getLocalRef() {
int temp = 10; // temp is a local variable, destroyed after function ends
return temp; // Returning reference to temp (dangling reference)
}
int main() {
int& ref = getLocalRef(); // ref is a dangling reference
cout << ref << endl; // Undefined behavior: may output garbage or crash
return 0;
}
The solution: ensure that the variable to which the reference is bound has a “long lifetime” (like global variables, heap variables, or variables in the main function).
4) Will not trigger the copy constructor
Because pass by reference does not copy data, when passing custom class objects, the copy constructor will not be called— this is also a key reason for its efficiency compared to pass by value. For example:
class MyClass {
public:
MyClass() { cout << "Default constructor" << endl; }
MyClass(const MyClass& other) { cout << "Copy constructor" << endl; }
};
void func(MyClass& obj) {} // Pass by reference
int main() {
MyClass a; // Outputs "Default constructor"
func(a); // No copy constructor output (directly binding alias)
return 0;
}
2.5 Suitable Scenarios for Pass by Reference
Scenario 1: Passing Large Objects/Containers, while avoiding copies
For example, <span><span>std::vector</span></span>, <span><span>std::map</span></span>, or custom large classes (like <span><span>Image</span></span>, <span><span>DataBuffer</span></span>), using pass by reference can save a lot of copy time. For example:
// Processing large vector, using const reference to avoid copy and prohibit modification
void processBigVector(const vector<int>& bigVec) {
for (int val : bigVec) {
// Read-only operation
}
}
Scenario 2: Functions that need to modify original data For example, implementing “sorting functions” or “data update functions,” using references directly modifies the original data without needing to pass results through return values. For example:
// Swapping values of two integers, directly modifying original variables using references
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
Scenario 3: Implementing multiple return values (more concise than structures) C++ functions can only return one value, but using reference parameters can achieve “multiple return values”— for example, calculating the “sum” and “average” of an array:
#include <vector>
using namespace std;
// Returning sum and average through two reference parameters
void calculateSumAvg(const vector<int>& arr, int& sum, double& avg) {
sum = 0;
for (int val : arr) {
sum += val;
}
avg = arr.empty() ? 0 : (double)sum / arr.size();
}
int main() {
vector<int> arr = {1, 2, 3, 4, 5};
int sum;
double avg;
calculateSumAvg(arr, sum, avg);
cout << "Sum:" << sum << ", Average:" << avg << endl; // Outputs "Sum: 15, Average: 3"
return 0;
}
Part 3Pass by Pointer
3.1 What is Pass by Pointer? What is the Essential Difference from Reference?
A pointer is a variable that stores the memory address of another variable— it has its own independent memory space (for example, occupying 8 bytes in a 64-bit system), storing the address of another variable. The core of pointer passing is “passing a copy of the pointer“— the formal parameter received by the function is a copy of the actual parameter pointer, both pointing to the same memory, but the formal parameter itself is independent (modifying the formal parameter’s target does not affect the actual parameter).
Let’s clarify the core differences between “pointer passing” and “reference passing”:
|
Comparison Dimension |
Reference Passing |
Pointer Passing |
|
Memory Space |
No independent space (alias) |
Has independent space (stores address) |
|
Initialization Requirement |
Must be immediately bound to a variable |
Can be defined first, assigned later |
|
Modification of Target |
Once bound, cannot change target |
Can change target at any time |
|
Null Value Support |
Cannot point to nullptr |
Can point to nullptr |
For example, when calling <span><span>increment(&num)</span></span>, the actual parameter is the address of <span><span>num</span></span> (<span><span>&num</span></span>), and the formal parameter <span><span>int* x</span></span> is a copy of this address—<span><span>x</span></span> stores the address of <span><span>num</span></span>, so dereferencing <span><span>*x</span></span> can operate on <span><span>num</span></span><span><span>; however, if you modify </span></span><code><span><span>x</span></span> itself (for example, making <span><span>x</span></span> point to another variable <span><span>temp</span></span>, the actual parameter pointer will not be affected because <span><span>x</span></span> is just a copy.
Example:
#include <iostream>
using namespace std;
// Formal parameter x: copy of actual parameter pointer, stores the address of num
void increment(int* x) {
// Safety check: avoid null pointer access
if (x == nullptr) {
cout << "Error: Pointer is null!" << endl;
return;
}
(*x)++; // Dereference: access num's memory through address, modifying original data
cout << "Value of *x inside the function:" << *x << endl; // Outputs 6
cout << "Address of x inside the function (address of the pointer itself):" << &x << endl; // x is a copy, address is independent
}
int main() {
int num = 5;
int* ptr = # // ptr stores the address of num (for example, 0x0012ff44)
cout << "Address stored in ptr outside the function:" << ptr << endl; // Outputs 0x0012ff44
cout << "Address of ptr itself outside the function:" << &ptr << endl; // For example, 0x0012ff40 (address of actual parameter pointer)
increment(ptr); // Passes a copy of ptr (stores 0x0012ff44)
cout << "Value of num outside the function:" << num << endl; // Outputs 6, original data modified
return 0;
}
3.2 Four Key Characteristics of Pass by Pointer
1) Highest flexibility, but lowest safety
The “modifiable target” of pointers is its greatest advantage— for example, when traversing a linked list, a pointer can point from the “current node” to the “next node”; but this is also a risk point: if not handled properly, it is easy to encounter null pointers (pointing to <span><span>nullptr</span></span>) or wild pointers (pointing to destroyed memory).
For example, a typical scenario of a wild pointer:
// Error example: returning a pointer to a local variable
int* getLocalPtr() {
int temp = 10; // temp is a local variable, destroyed after function ends
return &temp; // Returning address of temp (wild pointer)
}
int main() {
int* p = getLocalPtr(); // p is a wild pointer, pointing to destroyed memory
cout << *p << endl; // Undefined behavior: may output garbage, crash
return 0;
}
Ways to avoid pitfalls:
- Always check pointers before use
<span><span>nullptr</span></span>(using<span><span>if (p != nullptr)</span></span>); - Avoid returning pointers to local variables;
- For pointers allocated in dynamic memory (using
<span><span>new</span></span>), they must be<span><span>deleted</span></span>after use to avoid memory leaks.
2) The only way to pass arrays In C++, the array name is essentially “a pointer to the first element”; when passing an array, it actually passes the pointer to the first element (i.e., pointer passing). For example:
// Passing an array: arr is a pointer to the first element, len is the array length (must be explicitly passed, array name does not contain length information)
void printArray(int* arr, int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " "; // arr[i] is equivalent to *(arr + i)
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]); // Calculate array length
printArray(arr, len); // Passes pointer to first element (arr) and length
return 0;
}
It is important to note that when passing an array, the entire array is not copied; only the address of the first element is passed, so the array length must be explicitly passed (otherwise the function will not know how many elements are in the array).
3) Double pointers: modifying the target of a single pointer
As mentioned earlier, “when passing pointers, modifying the target of the formal parameter pointer does not affect the actual parameter”— so if we want to modify the target of a single pointer in a function (like dynamically allocating memory), the answer is double pointers (pointer to a pointer, <span><span>T**</span></span>).
For example, to allocate dynamic memory for a single pointer in a function:
#include <iostream>
using namespace std;
// Double pointer: ptr is the address of the single pointer arr, *ptr is arr itself
void allocateMemory(int** ptr, int size) {
if (ptr == nullptr) return;
// Allocate memory for single pointer arr (*ptr = arr)
*ptr = new int[size];
// Initialize array
for (int i = 0; i < size; i++) {
(*ptr)[i] = i; // (*ptr)[i] is equivalent to arr[i]
}
}
int main() {
int* arr = nullptr; // Single pointer, initially null
int size = 5;
allocateMemory(&arr, size); // Passes the address of the single pointer (double pointer)
// Use the array
for (int i = 0; i < size; i++) {
cout << arr[i] << " "; // Outputs 0 1 2 3 4
}
// Free memory (to avoid leaks)
delete[] arr;
arr = nullptr; // Avoid wild pointer
return 0;
}
The key logic here is that <span><span>&arr</span></span> is the address of the single pointer <span><span>arr</span></span>, so in the function, <span><span>*ptr</span></span> is actually <span><span>arr</span></span> itself, thus <span><span>*ptr = new int[size]</span></span> is equivalent to directly modifying the target of <span><span>arr</span></span><span><span>, making it point to the newly allocated heap memory.</span></span>
4) Compatible with C language code
C language does not have the “reference” feature, so all scenarios that require “operating on original data” use pointers— if our C++ project needs to call C library functions (like <span><span>stdio.h</span></span>, <span><span>string.h</span></span> functions), we must use pointer passing. For example, calling C’s <span><span>strcpy</span></span> function:
#include <cstring> // C library string functions
using namespace std;
int main() {
char dest[20];
const char* src = "hello world";
strcpy(dest, src); // C function, parameter is a pointer
cout << dest << endl; // Outputs hello world
return 0;
}
3.3 Suitable Scenarios for Pass by Pointer
Scenario 1: Handling Optional Parameters (allowing nullptr)
If a function’s parameter is “optional” (like an “optional output parameter”), using pointer passing is most appropriate— when the parameter is not needed, simply pass <span><span>nullptr</span></span>. For example:
#include <iostream>
using namespace std;
// Calculate the square of x, result is an optional output parameter (pass nullptr if not needed)
void square(int x, int* result = nullptr) {
int res = x * x;
if (result != nullptr) {
*result = res; // Assign to result when output is needed
}
cout << x << " squared is:" << res << endl; // Must print result
}
int main() {
int res;
square(5, &res); // Output needed, pass address of res
cout << "Stored result:" << res << endl; // Outputs 25
square(6); // No output needed, pass nullptr (default value)
return 0;
}
Scenario 2: Modifying the Target of a Single Pointer (must use double pointers)
For example, dynamic memory allocation, inserting/deleting linked list nodes (modifying head pointer), tree node operations, etc., can only use double pointers or “pointer references” (<span><span>T*&</span></span>).
Scenario 3: Passing Arrays or Dynamic Memory (heap objects)
Array passing can only use pointers (with length parameter); heap objects (allocated with <span><span>new</span></span>) must also be accessed using pointers, and naturally, pointer passing is used.
Scenario 4: Compatibility with C Language Code or Legacy C++ Code
If the project needs to interact with C code or maintain old C++ code (that does not use reference features), pointer passing must be used.
Part 4Comparison of Three Passing Mechanisms
We will conduct a comprehensive comparison from seven dimensions: “underlying implementation,” “performance,” “safety,” “practical scenarios,” etc., to help everyone make quick decisions:
|
Comparison Dimension |
Pass by Value |
Pass by Reference |
Pass by Pointer |
|
Content Passed |
Complete data copy of the actual parameter |
Alias of the actual parameter (binding address) |
Copy of the actual parameter pointer (stores address) |
|
Underlying Memory Overhead |
High (copying all data, significant for large objects) |
Low (only binding address, no data copy) |
Low (only copying address, 4/8 bytes) |
|
Can Modify Original Data |
No (only modifies copy) |
Yes (const can prohibit) |
Yes (const can prohibit, must dereference) |
|
Null Value Support |
Not applicable (passing data) |
Not supported (cannot bind to nullptr) |
Supported (can pass nullptr, must check) |
|
Lifetime Dependency |
No (formal parameter independent) |
Yes (reference ≤ actual parameter lifetime) |
Yes (memory pointed to by pointer must be valid) |
|
Syntactic Complexity |
Simple (directly passing value) |
Simple (& declaration, direct access) |
More complex (* dereference, & take address) |
|
Copy Constructor Invocation |
Will (when passing objects) |
Will not (no data copy) |
Will not (no data copy) |
|
Core Applicable Scenarios |
Small data, read-only operations, safety prioritized |
Large data, need modification, non-null parameters |
Optional parameters, double pointers, compatible with C code |
|
Common Errors |
Passing large objects leading to poor performance |
Binding temporary variables (non-const), dangling references |
Null pointer not checked, wild pointers, memory leaks |
Part 5Practical Decision-Making Process
5.1 Three Steps to Choose Parameter Passing (No More Confusion)
In actual development, there is no need to memorize; just follow this process:
1) First Step: Determine if modification of original data is needed?
- Not allowed to be null (parameter must be valid) → use pass by reference (safe, no null pointer risk);
- Allowed to be null (optional parameter) → use pass by pointer (need to add nullptr check).
- Small data (basic types, small structures) → use pass by value (safe and simple);
- Large data (large objects, containers) → use const reference (efficient, prohibits modification);
- If no modification is needed → consider data size:
- If modification is needed → consider whether parameters can be null:
2) Second Step: Determine if compatibility with C code is needed?
- Yes → must use pass by pointer;
- No → prioritize using references (syntax is concise, high safety).
3) Third Step: Determine if modification of pointer target is needed?
- Yes (like dynamic memory allocation, modifying linked list head pointer) → use double pointers or pointer references;
- No → follow the choices from the first and second steps.
5.2 The Five Most Common Pitfalls (Pitfall Guide)
Pitfall 1: Using pass by value for large objects
For example, passing <span><span>vector<int> bigVec(1000000, 1)</span></span> will copy 1 million <span><span>int</span></span>, directly leading to performance collapse. Avoid this by using <span><span>const vector<int>&</span></span>.
Pitfall 2: Reference binding to temporary variables (non-const)
For example, <span><span>void func(string& s) {}</span></span>, calling <span><span>func("hello")</span></span> will result in a compilation error. Avoid this by using <span><span>const string&</span></span><span><span>, allowing binding to temporary variables.</span></span>
Pitfall 3: Returning references/pointers to local variables
For example, the previous <span><span>getLocalRef()</span></span> and <span><span>getLocalPtr()</span></span> will return after the variable has been destroyed, forming dangling references/wild pointers. Avoid this by returning global variables, heap variables, or directly returning values (small data).
Pitfall 4: Pointers not checked for nullptr
For example, <span><span>void func(int* x) { (*x)++; }</span></span>, calling <span><span>func(nullptr)</span></span> will crash. Avoid this by checking all pointers before use, adding <span><span>if (x != nullptr)</span></span><code><span><span> check.</span></span>
Pitfall 5: Not releasing memory after dynamic allocation
For example, <span><span>int* p = new int[5];</span></span>, if not <span><span>delete[] p</span></span><code><span><span>, it will lead to memory leaks.</span></span><span><span> Avoid this by using smart pointers (</span></span><code><span><span>unique_ptr</span></span><span><span>, </span></span><code><span><span>shared_ptr</span></span><span><span>) to manage dynamic memory, or strictly follow the principle of "who allocates, who releases." </span></span>
Conclusion
The three parameter passing mechanisms in C++ essentially represent a trade-off between “efficiency” and “safety” :
- Pass by value is the “safety faction,” suitable for small data and read-only scenarios, but has high copy overhead;
- Pass by reference is the “balance faction,” balancing efficiency and safety, making it the first choice in most C++ scenarios;
- Pass by pointer is the “flexibility faction,” suitable for optional parameters and compatibility with C code, but requires manual avoidance of null pointer/wild pointer risks.
When writing code, we should not pursue “one universal method,” but choose based on specific scenarios— for example, using pass by value for passing <span><span>int</span></span>, using const reference for passing <span><span>vector</span></span>, and using pointers for optional parameters. Only by understanding the underlying logic and applicable boundaries of each mechanism can we write efficient, safe, and maintainable C++ code.
Recommended Previous Articles
C/C++ Practical: KVStore Storage Project
Writing a Video Player with Qt+FFmpeg (with code)
With this level of C++, do you still want to enter big companies like Alibaba, Tencent, etc.?
Click below to follow 【Linux Tutorials】 to get programming learning routes, project tutorials, resume templates, big company interview questions in PDF format, big company interview experiences, programming communication circles, and more.