References¶
Pass By Copy/Reference¶
Computer literature has terminology for different ways of parameter passing
Terms: “Pass by copy”, “pass by reference”
Terms: “input parameter”, “output parameter”, “input/output parameter”
Pass by copy: callee receives a copy of caller’s value (⟶ function cannot modify caller’s value)
Pass by reference: callee receives a reference to caller’s value (⟶ callee can modify caller’s value)
This terminology is language independent (Pascal?)
Pointers Can Be Left Uninitialized¶
Pointer are evil: can be left uninitilized …
int main()
{
int* pi;
*pi = 666; // <--- EVIL!
return 0;
}
$ ./code/c++03-references-uninitialized-pointer
Segmentation fault (core dumped)
References Cannot (Easily) Be Left Uninitialized¶
C++ does not let you leave references uninitialized:
int main()
{
int& i;
i = 666; // <--- only potentially evil
return 0;
}
code/c++03-references-uninitialized-reference.cpp: In function ‘int main()’:
code/c++03-references-uninitialized-reference.cpp:3:10: error: ‘i’ declared as reference but not initialized
3 | int& i;
| ^
Copy? Reference? C? Pointers!¶
In C, parameters are passed by copy only
#include <gtest/gtest.h> void pass_by_copy(int j) { j = 666; } TEST(references_suite, copy) { int i = 42; pass_by_copy(i); ASSERT_EQ(i, 42); }
But: there are addresses (pointers) that can be passed (by copy 🤨)
Voila, done!
As long as one can handle those
&
and*
, everything is okA little tedious though: take address (
&
), dereference address (*
)⟶ error prone
#include <gtest/gtest.h> void pass_by_pointer(int* j) { *j = 666; // <--- dereference j explicitly } TEST(references_suite, pointer) { int i = 42; pass_by_pointer(&i); // <--- take address of i explicitly ASSERT_EQ(i, 666); }
C++: True References¶
C++ adds another meaning to the
&
operator: referenceUsed in function declaration only
Every use looks like regular variable/parameter access
Internally addresses are taken, just like pointers - only easier
Advantage: references cannot (easily) dangle, or be
NULL
#include <gtest/gtest.h>
void pass_by_reference(int& j)
{
j = 666; // <--- dereference j implicitly
}
TEST(references_suite, reference)
{
int i = 42;
pass_by_reference(i); // <--- take address of i implicitly
ASSERT_EQ(i, 666);
}
And const
? Pointers?¶
In C, there is the
const
keyword which can be applied to pointers tooMeaning: “this is the address of something that must not be modified”
#include <gtest/gtest.h>
void pass_by_const_pointer(const int* j)
{
*j = 666; // <--- error: *j is const
}
TEST(references_suite, const_pointer)
{
int i = 42;
pass_by_const_pointer(&i); // <--- pass address to *const* i
}
code/pass-by-const-pointer.cpp:5:8: error: assignment of read-only location ‘* j’
5 | *j = 666; // <--- error: *j is const
| ~~~^~~~~
const
References¶
#include <gtest/gtest.h>
void pass_by_const_reference(const int& j)
{
j = 666; // <--- error: j is const
}
TEST(references_suite, const_reference)
{
int i = 42;
pass_by_const_reference(i); // <--- pass reference to const i
ASSERT_EQ(i, 666);
}
code/pass-by-const-reference.cpp:5:7: error: assignment of read-only reference ‘j’
5 | j = 666; // <--- error: j is const
| ~~^~~~~
const
Reference: Substitute For “Pass by Copy”¶
Pass by copy protects from accidental modification
Can be expensive though
#include <gtest/gtest.h> struct person { char firstname[128]; char lastname[128]; }; void pass_by_copy_expensive(person p) { // ... do something with p, read-only ... } TEST(references_suite, copy_expensive) { person p{"Joerg", "Faschingbauer"}; pass_by_copy_expensive(p); // <--- copying 256 bytes to callee }
const
references to the rescueAmount of bytes copied is the size of a pointer, no matter what the target type is
#include <gtest/gtest.h> struct person { char firstname[128]; char lastname[128]; }; void pass_by_const_reference_cheap(const person& p) { // ... do something with &p, read-only ... } TEST(references_suite, const_reference_cheap) { person p{"Joerg", "Faschingbauer"}; pass_by_const_reference_cheap(p); // <--- copying 8 bytes (a pointer) to callee }