An introduction to pointers and references in C++ (with elements of C++11 and C++14). The presentation introduces the readers to the concepts of pointers and references through the pragmatic need of writing a swap function between integers. Generic programming notions (e.g., type constructors) are adopted when useful for the explanation.
4. Swapping integers
Assume that we are wri-ng a program in which we o3en need to
swap two integers
┌──────────────────┐
│ │
│ ▼
┌───┐ ┌───┐
a:│ 2 │int b:│ 5 │int
└───┘ └───┘
▲ │
│ │
└──────────────────┘
9. First a(empt
At swap_int invoca*on:
• The values in a and b get copied into the local variables x and y
• The values in x and y get swapped, but...
• The values in a and b remain untouched
10. First a(empt
void swap_int(int x, int y) {
...
}
int main() {
int a = 2, b = 5;
swap_int(a, b); // x and y are copies of a and b
// hence, a and b remain unchanged
}
11. The swap problem
We conclude that it is not possible to write the swap_int
func1on if we pass parameters by value
14. Memory model
In C++, memory is modeled as a sequence of memory cells, where
each cell is of size 1 byte
┌──────┬──────┬──────┬──────┐
... │ │ │ │ │ ...
└──────┴──────┴──────┴──────┘
◀─────▶
1 byte
15. Memory model
Each memory cell is associated with a unique memory address
┌──────┬──────┬──────┬──────┐
... │ │ │ │ │ ...
└──────┴──────┴──────┴──────┘
0x7345
16. Memory model
Memory addresses are nothing more than numbers1
┌──────┬──────┬──────┬──────┐
... │ │ │ │ │ ...
└──────┴──────┴──────┴──────┘
0x7345
1
The 0x prefix is to say that the numbers are given in hexadecimal nota6on
17. Memory model
Given a cell, its address is obtained as the address of the previous
one plus one
┌──────┬──────┬──────┬──────┐
... │ │ │ │ │ ...
└──────┴──────┴──────┴──────┘
0x7345 0x7346 0x7347
18. Memory model
This assures that:
• Every memory cell has its own unique address
• Given a cell, it is possible to compute the address of its
neighborhood
19. Variable iden+fiers
We usually do not care about where values are stored
int a
◀───────────▶
┌──────┬──────┬──────┬──────┐
... │//////│//////│ │ │ ...
└──────┴──────┴──────┴──────┘
0x7345 0x7346 0x7347
20. Variable iden+fiers
We simply manipulate values by referring to the iden%fier of the
corresponding variable
int a = 3;
a = a + 7; // we do not know where `a` is stored in memory
21. Addresses
However, there might be situa2ons in which we would like to refer
to the in-memory loca-on of a value, rather than to the value itself
22. The ampersand
In order to manipulate the address of a variable, we prefix the
variable iden8fier with the ampersand symbol &
23. The ampersand
int a = 5;
std::cout << "value of a: " << a
<< std::endl;
std::cout << "address of a: " << &a
<< std::endl;
24. The address-of operator
When the ampersand symbol & precedes an expression2
, it plays
the role of a unary operator (the address-of operator)
int a = 7;
std::cout << &a; // conceptually: address_of(a);
2
Note that there exist expressions on which it is not possible to apply the address-of operator
25. The address-of operator
Note that if a value occupies more than one memory cell, the
address-of operator returns the address of the first memory cell
int a
◀───────────▶
┌──────┬──────┬──────┬──────┐
... │//////│//////│ │ │ ...
└──────┴──────┴──────┴──────┘
0x7345 0x7346 0x7347
▲
│
│
&a
26. A formal defini.on of copy
A copy is a something that is equal to the original but not iden)cal
to it
int a = 7;
int b = a;
a == b; // b is equal to a (they both contain 7)
&a != &b; // b is not a (they live in different memory locations)
31. Pointer to int
The data type of a variable storing the address of an int is int*.
Such a data type is called pointer to int
int* address_of_a = &a
32. Pointers
Variables that store addresses are called pointers
It is possible to create pointers to any data type3
, for built-in and
user-defined types alike
3
As long as the data type is at least of size 1 byte
33. For any data type T, T* is the data type pointer to T
34. Examples
int* a; // pointer to int
char* b; // pointer to char
float* c; // pointer to float
int** d; // pointer to pointer to int
36. Pointers as type constructors
The * symbol plays the role of a type constructor
A type constructor is a func0on that takes as input some type T
and returns some other type T'
48. Dereferencing
The dereference operator * makes possible to access the content
pointed to by a pointer4
int* ptr = ...
std::cout << "the value pointed to is: " << *ptr;
4
The dereference operator is also called the indirec'on operator
49. Dereferencing
The expression *ptr reads "the value pointed to by ptr"
int* ptr = ...
std::cout << "the value pointed to is: " << *ptr;
51. Dereferencing
In other words, the expression *ptr may also appear on the le4-
hand side of the assignment operator
int* ptr = ...
*ptr = 7;
int b = 5 + *ptr; // b contains 12
52. Same symbol, two meanings
// * is a type constructor
int* ptr_a = &a;
// * is an operator
std::cout << "pointed value: " << *ptr_a;
53. Trivia 1: What's the output?
int a = 7;
int* ptr = &a;
*ptr = 12;
std::cout << *ptr << std::endl;
std::cout << a << std::endl;
54. Trivia 2: What's the output?
int a = 7;
int* ptr = &a;
std::cout << &ptr << std::endl;
std::cout << &a << std::endl;
57. The "box" metaphor
Just as a vector is a container of many elements, we can say that a
pointer is a container of one element5
5
More formally, the similarity holds because both of them are functors
58. The "box" metaphor
// we put some values in the container
std::vector<int> v = {1, 2, 3, 4, 5};
// accessing the 3rd element
// in the container
std::cout << v[2];
59. The "box" metaphor
// we "put" a value in the container
int* p = &a;
// accessing the only element
// in the container
std::cout << *p;
62. Unini$alized pointers
As with any local variable, it is not possible to foresee the value of
unini6alized pointers
int* ptr; // uninitialized pointer
63. Unini$alized pointers
The pointer may hold any value. Such a value will be erroneously
intended as the address of a pointed value
int* ptr; // uninitialized pointer
64. Unini$alized pointers
There does not exist any way to understand whether a pointer is in
fact poin4ng to a valid value
int* ptr; // uninitialized pointer
65. Null pointers
We would like to ini#alize pointers even in the absence of a
sensible value
int* ptr = ???;
66. Null pointers
Pointers can be ini-alized to null in order to indicate that the
pointer is currently not poin-ng to any valid value6
int* ptr = nullptr; // since C++11
int* ptr = NULL; // C++03
6
In terms of the box metaphor, this amounts to no4fying that the container is empty
67. Solving the swap problem
Can we use pointers to write a working version of the swap_int
func4on?
68. Idea: passing to swap_int the variable addresses (as
opposed to their values)
72. swap_int with pointers
At swap_int invoca*on:
• The addresses of a and b get copied into the pointers x and y
(which are local to swap_int)
• The values pointed to by x and y (i.e., a and b) get swapped
• At the end of swap_int, x and y get destroyed
73. How to invoke swap_int?
int main() {
int a = 2;
int b = 5;
swap_int(???, ???);
}
74. How to invoke swap_int?
int main() {
int a = 2;
int b = 5;
swap_int(&a, &b);
}
75. Passing by address
If a func(on receives addresses in place of values, we say that
parameter are passed by address
void swap_int(int* x, int* y);
76. Passing by address
However, since we pass address copies, passing by address is just a
par6cular case of passing by value
void swap_int(int* x, int* y);
77. Passing by address
Passing by address allows...
• propaga'ng altera'ons in the callee func'on also in the calling
func'on
• rapidly accessing big data, since we copy its address as opposed
to its value
81. Iden%fiers
When declaring a variable, we need to specify its data type and
iden*fier
Both these aspects cannot be modified later on in the program
82. Iden%fier aliases
However, C++ allows introducing addi5onal iden5fiers (aliases) for
a variables any5me in the code
int a = 5;
int& b = a; // b is a new identifier (alias) for a
83. Iden%fier aliases
b is an alias for a, and its data type is int&
int a = 5;
int& b = a; // b is a new identifier (alias) for a
84. References
In C++, iden*fier aliases are called references7
int a = 5;
int& b = a; // b is a reference to a
7
Star'ng from C++11, references have been renamed lvalue references
85. For a data type T, T& is the data type reference to T
86. Examples
int& a = b; // a is an alias for b (where b is of type int)
float& c = d // c is an alias for d (where d is of type float)
int& e = b; // e is an alias for b, hence an alias for a
int*& f = g; // f is an alias for g (where g is of type int*)
87. Trivia 1: what's the output?
int a = 5;
int& b = a;
b = 7;
std::cout << a;
88. Trivia 1: what's the output?
int a = 5;
int& b = a; // b is a new name for a
b = 7;
std::cout << a; // the output will be 7
89. Aliases are indis+nguishable
Once ini'alized, using either the new or the old iden'fier is a
ma7er of indifference
int a = 5;
int& b = a; // b is a new name for a
// using b is the same as using a (and vice-versa)
std::cout << b;
90. Aliases are indis+nguishable
That is, the reference is the referent8
int a = 5;
int& b = a; // b is a new name for a
// using b is the same as using a (and vice-versa)
std::cout << b;
8
While holding conceptually, the C++11 specifica8on qualifies the reference and the referent as two different
en88es
91. & as a type constructor
As with *, also & can be intended as a type constructor
92. & as a type constructor
Construc)ng the type "reference to float"
• Input type: float
• Type constructor: &
• Output type: float&
93. Same symbol, two meanings
int a = 5;
// & is a type constructor
int& b = a;
// & is an operator
std::cout << &a;
94. Solving the swap problem
Can we use references to write a working version of the swap_int
func3on?
99. swap_int with references
Note that the func,on body is the same as in our first a5empt
void swap_int(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}
100. How to invoke swap_int?
int main() {
int a = 2;
int b = 5;
swap_int(???, ???);
}
101. How to invoke swap_int?
int main() {
int a = 2;
int b = 5;
swap_int(a, b);
}
102. How to invoke swap_int?
Note that we invoke swap_int as any other func3on
int main() {
int a = 2;
int b = 5;
swap_int(a, b);
}
103. swap_int with references
References allow us to write swap_int in an easy and safe
manner
• Easy: we do not need to use the dereference operator *
everywhere
• Safe: the program will not compile if we try to pass nullptr or
temporary values
104. Passing by reference
If a func(on receives iden%fier aliases in place of values, we say
that parameter are passed by reference
void swap_int(int& x, int& y);
107. References are not assignable
References can be ini#alized, but not (re)-assigned
int a = 5;
int& b; // b is an alias for what?
b = a;
108. Does it work?
void countdown(int& number) {
std::cout << number << std::endl;
if (number != 0)
countdown(number - 1);
}
109. The compile-,me error
Here's the error:
error: invalid initialization of non-const reference of
type 'int&' from an rvalue of type 'int'
110. The compile-,me error
We can safely replace the term rvalue with temporary:
error: invalid initialization of non-const reference of
type 'int&' from a temporary of type 'int'
111. The compile-,me error
The temporary the error is referring to is number - 1
void countdown(int& number) {
std::cout << number << std::endl;
if (number != 0)
countdown(number - 1);
}
112. Reference-to-const
The correct version reads:
void countdown(int const& number) {
std::cout << number << std::endl;
if (number != 0)
countdown(number - 1);
}
113. Reference-to-const
Only constant references can bind to temporary values
void countdown(int const& number) {
std::cout << number << std::endl;
if (number != 0)
countdown(number - 1);
}
114. Reference-to-const
This is why the input parameter number has to be of type int
const&, rather than int&
void countdown(int const& number) {
std::cout << number << std::endl;
if (number != 0)
countdown(number - 1);
}
116. Dangling references
Although it compiles, the add func2on causes an undefined
behavior
double& add(double x, double y) {
double z = x + y;
return z;
}
117. Dangling references
This is because we are returning an alias for z, which will be
destroyed as soon as we exit the func7on
double& add(double x, double y) {
double z = x + y;
return z;
}
118. Dangling references
In other words, the add func0on returns a dangling reference, that
is, a reference for a non-exis0ng object
double& add(double x, double y) {
double z = x + y;
return z;
}
int main() {
double r = add(5, 3);
std::cout << r;
}
119. Dangling references
Although this seems trivial when presented in contrived examples,
this is an easy mistake to make when wri9ng class' ge;ers and
se;ers
121. Bibliography
• S. B. Lippman, J. Lajoie, B. E. Moo, C++ Primer (5th Ed.)
• B. Stroustrup, The C++ Programming Language (4th Ed.)
• R. Lafore, Object Oriented Programming in C++ (4th Ed.)
• C++FAQ, SecJon 8