Reflection on C++ Parameter Passing
1 | Rectangle::Rectangle(const Rectangle& source){ |
This is a snippet from Mosh’s C++ course, I doubted that:
- Why we have to use
->to acessthis(pointer points to current object) ‘s member, while we can use dot operator.to accesssource‘s member. - What is the esstential of passing parameter by reference
- What are the differences between - Passing parameters directly by variable / Passing parameters by referencing / Passing parameters by pointers
Great question! Let’s break this down step-by-step to clarify why -> is used for this while the dot operator (.) is used for source in this copy constructor.
Access Way Question
This is a copy constructor for a Rectangle class in C++. It takes a constant reference to another Rectangle object (source) and copies its member variables (width, height, color) into the current object being constructed. The keyword this is a pointer to the current object.
Here’s the code again for reference:
1 | Rectangle::Rectangle(const Rectangle& source) { |
Why this Uses ->?
thisis a pointer: In C++,thisis a special keyword that represents a pointer to the current object. Since it’s a pointer (of typeRectangle*in this case), we use the arrow operator->to access the members of the object it points to.- Arrow operator (
->): The->operator is used to dereference a pointer and access a member of the object it points to. So,this->widthmeans “access thewidthmember of the object thatthispoints to.” We can also use a complicated OG style to do this(*this).width, which is equivalent to arrow operator access. - Why not
.withthis?: The dot operator (.) is used for objects or references, not pointers. If we triedthis.width, it would be a syntax error becausethisis a pointer (address), not an object itself.
Why source Uses .?
sourceis a reference: The parametersourceis declared asconst Rectangle& source, meaning it’s a reference to aRectangleobject (not a pointer). In C++, a reference acts like an alias for the object it refers to, so we can use the dot operator (.) to access its members directly.- Dot operator (
.): The.operator is used to access members of an object or a reference to an object. So,source.widthmeans “access thewidthmember of thesourceobject.” - Why not
->withsource?: If we triedsource->width, it would be incorrect becausesourceis not a pointer—it’s a reference. The->operator is only for pointers.
Conclusion & Extension
this: A pointer (Rectangle*), so use->.source: A reference (const Rectangle&), so use..- This distinction comes from how C++ handles pointers versus references. Hope that clears it up! Let me know if we have more questions.
Refrence’s Feature in C++
In C++, a reference (e.g., Rectangle&) is a way to create an alias for an existing object. It’s not a separate object or a copy—it’s just another name for the same object in memory. When we use a reference, we’re directly manipulating the original object, as if we were using its original name.
When we pass an object by reference (using &), we’re passing an alias to the original object, not a copy. The function works directly with the original object.
1 | Rectangle::Rectangle(const Rectangle& source) { |
Here, source is a reference (const Rectangle&). It’s an alias for whatever Rectangle object is passed in, so accessing source.width is exactly the same as accessing the original object’s width.
- No copy is created: The function uses the original object directly via the reference. No duplication happens, so there’s no call to the copy constructor for the parameter itself.
- Original can be modified: If the reference isn’t const, changes to the parameter affect the original object.
- Performance benefit: Passing by reference is more efficient because it avoids copying the object, especially for large or complex objects.
- Use case: Pass by reference when we want to avoid copying (for efficiency) or when the function needs to modify the original object.
In our copy constructor:
- The
constensures source can’t be modified inside the constructor. - It’s still a reference, so no copy of source is made when passing it—just an alias to the original object.
A reference acts as an alias because, at the language level, C++ ensures that any operation on the reference (e.g., reading or writing) is performed directly on the object it refers to. Unlike a pointer, which stores an address and requires dereferencing, a reference is designed to be transparent—we use it like the object itself, with the dot operator (.), and the compiler handles the rest.
But how does this work under the hood?
What Exactly the Implementation of Reference
At a high level, references are a C++ abstraction, but at a low level (in the compiled machine code), they are typically implemented using pointers or direct memory access. Here’s a detailed look:
1. References Are Not Objects
- A reference doesn’t have its own memory storage. It’s not a separate entity like a variable or a pointer. Instead, it’s just a name for an existing object’s memory location.
- When we declare
Rectangle& ref = someRect;,refdoesn’t get its own memory—it’s bound to the memory of the objectsomeRect. - This is why we can’t have a “null reference” (unlike a null pointer) or reassign a reference to another object after initialisation. Once a reference is bound to an object, it’s permanently tied to it.
- We can never reassign a reference to another object after initialisation
2. Compiler’s Role
- The C++ compiler translates reference operations into direct memory accesses to the referred object. When we write
source.width, the compiler knowssourceis an alias for a specificRectangleobject and generates code to access that object’swidthmember directly. - In most cases, the compiler optimises references so they don’t add any runtime overhead( Runtime Overhead: Extra computing resources used during execution) compared to using the object directly.
3. References as Pointers Under the Hood
- In many implementations, references are internally treated like pointers by the compiler, but with syntactic sugar to make them easier to use. (Easier than pointers LOL)
- For example, when we pass
const Rectangle& source, the compiler might pass the memory address of theRectangleobject (like aconst Rectangle*). However, unlike a pointer, we don’t need to dereference it with*or->—the compiler automatically translatessource.widthto the equivalent of(*source).widthorsource->widthin the generated code. - The key difference is that references are safer and more restricted than pointers:
- References can’t be null (they must be initialised to a valid object).
- References can’t be reassigned to refer to another object.
- References don’t require explicit dereferencing syntax.
4. Memory Layout
- Suppose we have a
Rectangleobject like this:In memory,1
Rectangle r1{5, 5, "red"};
r1might look like:1
2[width: 5 | height: 5 | color: "red"]
^ Address: 0x1000 (example) - When we pass
r1as a reference:The1
Rectangle r2(r1); // Calls Rectangle::Rectangle(const Rectangle& source)
sourcereference is just another name for the memory at0x1000. Accessingsource.widthgoes straight to0x1000to read the value5. No copy of the object is made—only the address is used internally.
5. No Overhead for Passing
- Passing a reference is as efficient as passing a pointer because it typically involves passing a memory address (4 or 8 bytes on most systems). This is why
const Rectangle&is preferred over passing by value (which copies the entire object) in our copy constructor. - For example:
- By value: Copies
width,height,color(potentially expensive, especially ifcoloris astd::stringwith dynamic memory). - By reference: Passes a single address, no copying of the object’s data.
- By value: Copies
6. Const References
In our constructor,
const Rectangle& sourceadds a guarantee that the referred object won’t be modified. The compiler enforces this by preventing writes tosource’s members (e.g.,source.width = 10;would cause a compile-time error).Under the hood, the
constqualifier might translate to aconst Rectangle*in the generated code, ensuring the memory at that address isn’t altered.
Let’s imagine a simplified assembly-like view of our copy constructor with const Rectangle& source. Assume Rectangle has width (int), height (int), and color (string), and r1 is at address 0x1000.
Calling the Constructor:
1
Rectangle r2(r1);
- The compiler passes
r1’s address (0x1000) to the constructor, similar to a pointer.
- The compiler passes
Inside the Constructor:
sourceis effectively a name for address0x1000.source.widthtranslates to “read 4 bytes at0x1000” (assumingwidthis at offset 0).this->width = source.width;translates to “write that value to thewidthfield of the new object” (e.g., at0x2000ifr2is there).
Generated Code (Pseudo-Assembly):
1
2
3
4
5
6
7; Pass r1's address (0x1000) to constructor
mov register, 0x1000
; Read source.width (at 0x1000)
mov eax, [register + 0]
; Write to this->width (this at 0x2000)
mov [this + 0], eax
; Repeat for height, color...The actual implementation of it will be slightly different depends on different compiler.
Edge Cases and Notes
Reference initialisation:
- A reference must be initialised when declared:
1
2Rectangle& ref = r1; // OK
Rectangle& ref; // Error: uninitialised - This ensures a reference always aliases a valid object.
- A reference must be initialised when declared:
No Rebinding:
- Once bound, a reference can’t alias a different object:
1
2
3Rectangle r1, r2;
Rectangle& ref = r1;
ref = r2; // Doesn’t rebind! Assigns r2’s values to r1
- Once bound, a reference can’t alias a different object:
Temporary Objects:
A temporary object in C++ is an unnamed object created as a result of an expression, typically with a short lifetime.
1
Rectangle(1, 1, "blue") // Creates a temporary Rectangle object
In C++, non-const lvalue references (e.g.,
Rectangle&) can only bind to lvalues—objects that have a name and persist beyond a single expression. Temporaries, however, are rvalues—they’re fleeting, unnamed objects that typically don’t have a persistent identity.Here’s the key rule:
- A non-const lvalue reference (
Rectangle&) cannot bind to an rvalue (like a temporary). - A const lvalue reference (
const Rectangle&) can bind to an rvalue, and it extends the temporary’s lifetime to match the reference’s scope.
- A non-const lvalue reference (
References can bind to temporaries if declared as
const:1
2void func(const Rectangle& r);
func(Rectangle(1, 1, "blue")); // OK: temporary Rectangle
Passing Parameters by Value v.s. Reference v.s. Pointer
| Aspect | Pass by Value | Pass by Reference | Pass by Pointer |
|---|---|---|---|
| Syntax (Parameter) | Rectangle obj | Rectangle& obj | Rectangle* obj |
| Syntax (Access) | obj.width (dot) | obj.width (dot) | obj->width (arrow) |
| Copying | Full copy of object | No copy—just a reference | No copy—just an address |
| Performance | Slow for large objects | Fast (no copying) | Fast (no copying) |
| Memory Usage | High (duplicate object) | Low (reference, ~address) | Low (pointer, ~address) |
| Modification | Affects copy only | Affects original (unless const) | Affects original (unless const) |
| Pass Syntax | someFunction(r) | someFunction(r) | someFunction(&r) |
| Nullability | Always valid (copy made) | Always valid (bound to object) | Can be nullptr (needs check) |
| Use in Constructors | Rarely used (inefficient) | Common (const Rectangle&) | Possible but less common |
1. Passing by Value (Object Itself)
When we pass an object by value, a complete copy of the object is made and passed to the function.
1 | void someFunction(Rectangle obj) { |
Key Characteristics
- Copy created: The entire object is duplicated using the copy constructor.
- Original unaffected: Changes inside the function only affect the copy.
- Performance: Expensive for large objects due to copying overhead.
- Syntax: Uses the dot operator (.) to access members (e.g., obj.width).
2. Passing by Reference
When we pass by reference, we pass an alias to the original object, not a copy.
1 | void someFunction(Rectangle& obj) { |
With const
1 | void someFunction(const Rectangle& obj) { |
Key Characteristics
- No copy: The function works directly with the original object via the reference.
- Original modifiable: Unless const is used, changes affect the original.
- Performance: Efficient—no copying overhead.
- Syntax: Uses the dot operator (
.) because a reference acts like the object itself (e.g.,obj.width).
3. Passing by Pointer
When we pass by pointer, we pass the memory address of the object. The function works with that address and must dereference it to access the object.
1 | void someFunction(Rectangle* obj) { |
with const
1 | void someFunction(const Rectangle* obj) { |
Key Characteristics
- No copy: Only the address (a small value, typically 4 or 8 bytes) is passed, not the object itself.
- Original modifiable: The pointer allows modification of the original object unless const is used.
- Performance: Efficient—no copying of the object, just a pointer.
- Syntax: Uses the arrow operator (
->) to access members (e.g.,obj->width) because obj is a pointer. Alternatively, we could dereference with * and use the dot operator (e.g.,(*obj).width), but -> is more common. - Explicit address: Caller must pass the address using & (e.g.,
someFunction(&r)). - Nullable: Pointers can be nullptr, so we might need to check for validity.
Why Use const Rectangle& in the Copy Constructor?
In our example:
1 | Rectangle::Rectangle(const Rectangle& source) |
- Efficiency: Passing by reference avoids making an unnecessary copy of the Rectangle object being passed in.
- Safety: The const ensures the constructor doesn’t accidentally modify the source object, which is only meant to be copied from.
- Correctness: If it were Rectangle source (by value), the copy constructor would be called to create the parameter source, leading to infinite
- Title: Reflection on C++ Parameter Passing
- Author: Ricardo Pu
- Created at : 2025-04-11 10:40:32
- Updated at : 2025-04-12 19:15:27
- Link: https://ricardopotter.github.io/RicardoBlog/2025/04/11/Reflection-on-CPP-Parameter-Passing/
- License: This work is licensed under CC BY-NC-SA 4.0.