ri and i refer to the same object / memory location:
cout << i <<'\n'; // 2 cout << ri <<'\n'; // 2 i = 5; cout << i <<'\n'; // 5 cout << ri <<'\n'; // 5 ri = 88; cout << i <<'\n'; // 88 cout << ri <<'\n'; // 88
references cannot be “null”, i.e., they must always refer to an object
a reference must always refer to the same memory location
reference type must agree with the type of the referenced object
int i = 2; int k = 3; int& ri = i; // reference to i ri = k; // assigns value of k to i (target of ri) int& r2; // ❌ COMPILER ERROR: reference must be initialized double& r3 = i; // ❌ COMPILER ERROR: types must agree
const References
= Read-Only Access To An Object
int i = 2; intconst& cri = i; // const reference to i
cri and i refer to the same object / memory location
but const means that value of i cannot be changed through cri
reference type is deduced from right hand side of assignment
int i = 2; double d = 2.023; double x = i + d; auto & ri = i; // ri: int & autoconst& crx = x; // crx: double const&
Binding Rules
Rvalues vs. Lvalues
Lvalues = expressions of which we can get memory address
refer to objects that persist in memory
everything that has a name (variables, function parameters, …)
Rvalues = expressions of which we can’t get memory address
literals (123, "string literal", …)
temporary results of operations
temporary objects returned from functions
int a = 1; // a and b are both lvalues int b = 2; // 1 and 2 are both rvalues a = b; b = a; a = a * b; // (a * b) is an rvalue int c = a * b; // OK a * b = 3; // ❌ COMPILER ERROR: cannot assign to rvalue std::vector<int> read_samples(int n){ … } auto v = read_samples(1000);
voidswap(int& i, int& j){ … } int i = 0; swap(i, 5); // ❌ COMPILER ERROR: can't bind ref. to literal
Pitfalls
❌ Never Return A Reference To A Function-Local Object!
int& increase(int x, int delta){ x += delta; return x; } // ❌ local x destroyed intmain(){ int i = 2; int j = increase(i,4); // accesses invalid reference! }
Only valid if referenced object outlives the function!
int& increase(int& x, int delta){ x += delta; return x; // x references non-local int } // OK, reference still valid intmain(){ int i = 2; int j = increase(i,4); // OK, i and j are 6 now }
Careful With Referencing vector Elements!
⚠ References to elements of a std::vector might be invalidated after any operation that changes the number of elements in the vector!
vector<int> v {0,1,2,3}; int& i = v[2]; v.resize(20); i = 5; // ⚠ UNDEFINED BEHAVIOR: original memory might be gone!
Dangling Reference = Reference that refers to a memory location that is no longer valid.
The internal memory buffer where std::vector stores its elements can be exchanged for a new one during some vector operations, so any reference into the old buffer might be dangling.
Avoid Lifetime Extension!
References can extend the lifetime of temporaries (rvalues)
autoconst& r = vector<int>{1,2,3,4};
⇒ vector exists as long as reference r exists
What about an object returned from a function?
std::vector<std::string> foo(){ … }
✔ take it by value (recommended):
vector<string> v1 = foo(); auto v2 = foo();
😐 ignore it ⇒ gets destroyed right away
foo();
😐 get const reference to it ⇒ lifetime of temporary is extended