[go: up one dir, main page]

100% found this document useful (1 vote)
1K views349 pages

C Fundamentals Running Language Supports

fundamentals

Uploaded by

holamid
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
1K views349 pages

C Fundamentals Running Language Supports

fundamentals

Uploaded by

holamid
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 349

C++ Fundamentals

Hit the ground running with C++, the language that


supports tech giants globally

Antonio Mallia and Francesco Zoffoli


C++ Fundamentals
Copyright © 2019 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system,
or transmitted in any form or by any means, without the prior written permission of the
publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of
the information presented. However, the information contained in this book is sold
without warranty, either express or implied. Neither the authors, nor Packt Publishing,
and its dealers and distributors will be held liable for any damages caused or alleged to
authors be caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the
companies and products mentioned in this book by the appropriate use of capitals.
However, Packt Publishing cannot guarantee the accuracy of this information.
Author: Antonio Mallia and Francesco Zoffoli
Technical Reviewer: Rajesh Mani
Managing Editor: Vishal Kamal Mewada
Acquisitions Editor: Koushik Sen
Production Editor: Nitesh Thakur
Editorial Board: David Barnes, Ewan Buckingham, Shivangi Chatterji, Simon Cox,
Manasa Kumar, Alex Mazonowicz, Douglas Paterson, Dominic Pereira, Shiny Poojary,
Saman Siddiqui, Erol Staveley, Ankita Thakur, and Mohita Vyas
First Published: March 2019
Production Reference: 1080319
ISBN: 978-1-78980-149-1
Published by Packt Publishing Ltd.
Livery Place, 35 Livery Street
Birmingham B3 2PB, UK
Table of Contents

Preface   i

Getting Started   1

Introduction .....................................................................................................  2
The C++ Compilation Model ..........................................................................  2
Difference Between Header and Source Files ................................................... 4
Compiling a File into an Object File .................................................................... 7
Linking Object Files ............................................................................................... 8
Working with the main Function ........................................................................ 9
Exercise 1: Compiling and Executing the main Function ............................... 10
Built-in Data Types ........................................................................................  11
Primitive Data Types .......................................................................................... 11
Datatype Modifiers ............................................................................................. 12
Variable Definition .............................................................................................. 13
Demystifying Variable Initialization ................................................................. 14
Pointers and References ..............................................................................  15
Pointers ................................................................................................................ 15
References ........................................................................................................... 17
The const Qualifier ............................................................................................. 18
The Scope of Variables ....................................................................................... 22
Control Flow Statements .............................................................................  24
Selection Statement – if-else ............................................................................. 25
Selection Statement – switch ............................................................................ 28
Iteration Statement – for loop ........................................................................... 29
Iteration Statement – while loop ...................................................................... 32
Iteration Statement – do-while loop ................................................................. 32
Jump Statements – break and continue .......................................................... 32
The try-catch block .......................................................................................  33
Exercise 2: Counting the Number of Times a Specific Number
Appears in a Given List ....................................................................................... 35
Activity 1: Finding the Factors of 7 between 1 and 100 Using a
while Loop ............................................................................................................ 36
Arrays .............................................................................................................  36
Array Declaration ................................................................................................ 36
Array Initialization .............................................................................................. 37
Accessing the Values of an Array ...................................................................... 37
Multidimensional Arrays .................................................................................... 38
Activity 2: Defining a Bi-Dimensional Array and Initializing
Its Elements ......................................................................................................... 40
Summary ........................................................................................................  40

Functions   43

Introduction ...................................................................................................  44
Function Declaration and Definition ..........................................................  45
Defining a Function ............................................................................................. 47
Exercise 3: Calling a Function from main() ...................................................... 49
Local and Global Variables ..........................................................................  50
Working with Variable Objects .......................................................................... 52
Exercise 4: Using Local and Global Variables in a Fibonacci Sequence ........ 55
Passing Arguments and Returning Values .................................................  57
Pass by Value ....................................................................................................... 59
Exercise 5: Calculating Age using Pass by Value Arguments ......................... 59
Pass by Reference ............................................................................................... 61
Exercise 6: Calculating Incrementation of Age using Pass
by Reference ........................................................................................................ 62
Activity 3: Checking Voting Eligibility ................................................................ 63
Working with const References or r-value References ............................  63
Returning Values from Functions ..................................................................... 65
Returning by Value ............................................................................................. 67
Returning by Reference ..................................................................................... 68
Activity 4: Using Pass by Reference and Pass by Value .................................. 72
Const Parameters and Default Arguments ...............................................  72
Passing by const Value ....................................................................................... 72
Passing by const Reference ............................................................................... 73
Returning by const Value ................................................................................... 74
Returning by const Reference ........................................................................... 74
Default Arguments .......................................................................................  75
Namespaces ..................................................................................................  76
Activity 5: Organizing Functions in Namespaces ............................................ 78
Function Overloading ...................................................................................  79
Activity 6: Writing a Math Library for a 3D Game ........................................... 80
Summary ........................................................................................................  81

Classes   83

Introduction ...................................................................................................  84
Declaring and Defining a Class ....................................................................  84
The Advantages of Using Classes ...................................................................... 84
C++ Data Members and Access Specifiers ....................................................... 86
Static Members ................................................................................................... 90
Exercise 7: Working with Static Variables ........................................................ 92
Member Functions ........................................................................................  93
Declaring a Member Function ........................................................................... 94
Using const Member Functions ........................................................................ 94
The this Keyword ................................................................................................ 96
Exercise 8: Creating a Program Using the this Keyword to
Greet New Users ................................................................................................. 97
Non-Member Class-Related Functions ............................................................. 98
Activity 7: Information Hiding Through Getters and Setters ......................... 98
Constructors and Destructors .....................................................................  99
Constructors .....................................................................................................  100
Overloading Constructor ................................................................................  102
Constructor Member Initialization ................................................................  104
Aggregate Classes Initialization .....................................................................  105
Destructors .......................................................................................................  106
Exercise 9: Creating a Simple Coordinate Program to
Demonstrate the Use of Constructors and Destructors .............................  107
Default Constructor and Destructor .............................................................  108
Activity 8: Representing Positions in a 2D Map ............................................  108
Resource Acquisition Is Initialization .......................................................  109
Activity 9: Storing Multiple Coordinates of Different
Positions on a Map ..........................................................................................  111
Nested Class Declarations .........................................................................  111
Friend Specifier ...........................................................................................  113
Friend Functions ..............................................................................................  113
Friend Classes ...................................................................................................  113
Exercise 10: Creating a Program to Print the User's Height .......................  115
Activity 10: The AppleTree Class, which Creates an
Apple Instance ..................................................................................................  116
Copy Constructors and Assignment Operators ......................................  117
The copy Assignment Operator .....................................................................  119
The move-constructor and move-assignment Operator ............................  121
Preventing Implicit Constructors and Assignment Operators ...................  123
Operator Overloading ................................................................................  124
Activity 11: Ordering Point Objects ................................................................  127
Introducing Functors ..................................................................................  128
Activity 12: Implementing Functors ...............................................................  129
Summary ......................................................................................................  130

Generic Programming and Templates   133

Introduction .................................................................................................  134
Templates ....................................................................................................  135
Compiling the Template Code ........................................................................  136
Exercise 11: Finding the Bank Account of the User with the
Highest Balance ...............................................................................................  137
Using Template Type Parameters ..................................................................  139
Requirements of Template Parameter Types ..............................................  140
Defining Function and Class Templates ...................................................  144
Function Template ...........................................................................................  144
Class Templates ...............................................................................................  146
Dependent Types .............................................................................................  150
Activity 13: Reading Objects from a Connection ..........................................  151
Activity 14: Creating a User Account to Support
Multiple Currencies .........................................................................................  153
Non-Type Template Parameters ...............................................................  154
Activity 15: Writing a Matrix Class for Mathematical
Operations in a Game .....................................................................................  156
Making Templates Easier to Use ...............................................................  158
Default Template Arguments .........................................................................  158
Template Argument Deduction .....................................................................  160
Parameter and Argument Types ....................................................................  160
Activity 16: Making the Matrix Class Easier to Use ......................................  162
Being Generic in Templates .......................................................................  163
Activity 17: Ensuring Users are Logged in When Performing
Actions on the Account ...................................................................................  168
Variadic Templates .....................................................................................  170
Activity 18: Safely Performing Operations on the User Cart
with an Arbitrary Number of Parameters ....................................................  174
Writing Easy-to-Read Templates ...............................................................  175
Type Alias ..........................................................................................................  175
Template Type Alias ........................................................................................  179
Summary ......................................................................................................  179

Standard Library Containers and Algorithms   181

Introduction .................................................................................................  182
Sequence Containers ..................................................................................  182
Array ..................................................................................................................  183
Vector ................................................................................................................  184
Deque ................................................................................................................  188
List .....................................................................................................................  188
Forward-List ......................................................................................................  190
Providing Initial Values to Sequence Containers .........................................  190
Activity 19: Storing User Accounts .................................................................  191
Associative Containers ...............................................................................  192
Set and Multiset ...............................................................................................  192
Map and Multimap ..........................................................................................  194
Activity 20: Retrieving a User's Balance from their Given Username .......  196
Unordered Containers ...............................................................................  197
Container Adaptors ....................................................................................  199
Stack ..................................................................................................................  200
Queue ................................................................................................................  200
Priority Queue ..................................................................................................  201
Activity 21: Processing User Registration in Order ......................................  201
Unconventional Containers .......................................................................  202
Strings ...............................................................................................................  202
Exercise 12: Demonstrating Working Mechanism of the
c_str() Function ...............................................................................................  203
Pairs and Tuples ...............................................................................................  206
std::optional ................................................................................................  207
std::variant ...................................................................................................  210
Exercise 13: Using Variant in the Program ...................................................  211
Exercise 14: Visitor Variant .............................................................................  212
Activity 22: Airport System Management .....................................................  214
Iterators .......................................................................................................  215
Exercise 15: Exploring Iterator .......................................................................  218
Reverse Iterators .............................................................................................  219
Exercise 16: Exploring Functions of Reverse Iterator ..................................  220
Insert Iterators .................................................................................................  220
Stream Iterators ...............................................................................................  221
Exercise 17: Stream Iterator ...........................................................................  221
Iterator Invalidation ........................................................................................  222
Exercise 18: Printing All of the Customers' Balances ..................................  223
Algorithms Provided by the C++ Standard Template Library ................  224
Lambda .............................................................................................................  225
Read-Only Algorithms .....................................................................................  227
Modifying Algorithms ......................................................................................  229
Mutating Algorithms .......................................................................................  231
Sorting Algorithms ...........................................................................................  232
Binary Search Algorithms ...............................................................................  233
Numeric Algorithms ........................................................................................  233
Exercise 19: Customer Analytics ....................................................................  234
Summary ......................................................................................................  236

Object-Oriented Programming   239

Introduction .................................................................................................  240
Inheritance ..................................................................................................  240
Exercise 20: Creating a Program to Illustrate Inheritance in C++ ..............  242
Exercise 21: Using Multiple Inheritance to Create a "Welcome to
the Community" Message Application ..........................................................  248
Activity 23: Creating Game Characters .........................................................  249
Polymorphism .............................................................................................  250
Dynamic Binding ..............................................................................................  251
Virtual Methods ..........................................................................................  254
Exercise 22: Exploring the Virtual Method ....................................................  254
Activity 24: Calculating Employee Salaries ...................................................  258
Interfaces in C++ .........................................................................................  260
Activity 25: Retrieving User Information .......................................................  264
Dynamic Memory ........................................................................................  266
Safe and Easy Dynamic Memory ...............................................................  270
A Single Owner Using std::unique_ptr ...........................................................  272
Shared Ownership Using std::shared_ptr .....................................................  275
Activity 26: Creating a Factory for UserProfileStorage ................................  276
Activity 27: Using a Database Connection for Multiple Operations ..........  277
Summary ......................................................................................................  278

Appendix   281

Index   317
Preface
>
About

This section briefly introduces the authors, the coverage of this book, the technical skills you'll
need to get started, and the hardware and software requirements required to complete all of
the included activities and exercises.
ii | Preface

About the Book


C++ Fundamentals begins by introducing you to the C++ compilation model and syntax.
You will then study data types, variable declaration, scope and control flow statements.
With the help of this book, you'll be able to compile fully working C++ code and
understand how variables, references, and pointers can be used to manipulate the state
of the program. Next, you will explore functions and classes — the features that C++
offers to organize a program — and use them to solve more complex problems. You will
also understand common pitfalls and modern best practices, especially the ones that
diverge from the C++98 guidelines.
As you advance through the chapters, you'll study the advantages of generic
programming and write your own templates to make generic algorithms that work
with any type. This C++ book will guide you in fully exploiting standard containers and
algorithms, understanding how to pick the appropriate one for each problem.
By the end of this book, you will not only be able to write efficient code, but also
be equipped to improve the readability, performance, and maintainability of your
programs.

About the Authors


Francesco Zoffoli is a professional software engineer currently working for Bloomberg
LP in London. He became an enthusiast about modern C++ when joining the financial
industry after a master degree in Computer Science and Engineering at Politecnico di
Milano. He is passionate about programming languages, maintainable software, and
large distributed systems. He uses C++ for his projects as well as his day-to-day job to
deliver scalable, efficient, and resilient systems.
Antonio Mallia is a C++ enthusiast with half a decade of experience in the industry
as a software engineer and is currently pursuing a Ph.D. in computer science at NYU
Tandon School of Engineering in New York City. His research interest is mainly related
to information retrieval, with a strong focus on improving the efficiency of large-scale
systems. For this reason, C++ plays a vital role in most of his projects and contributes to
their success.
About the Book | iii

Objectives
• C++ compilation model
• Apply best practices for writing functions and classes
• Write safe, generic, and efficient code with templates
• Explore the containers that the C++ standard offers
• Discover the new features introduced with C++11, C++14, and C++17
• Get to grips with the core language features of C++
• Solve complex problems using object-oriented programming in C++

Audience
If you're a developer looking to learn a new powerful language, or are familiar with C++
but want to update your knowledge with the modern paradigms of C++11, C++14, and
C++17, this book is for you. To easily understand the concepts in the book, you must be
familiar with the basics of programming.

Approach
C++ Fundamentals perfectly balances theory and exercises. Each module is designed to
build on the previous module. The book contains multiple activities that use real-life
business scenarios for you to practice and apply your new skills in a highly relevant
context.

Minimum Hardware Requirements


For the optimal student experience, we recommend the following hardware
configuration:
• Processor: Intel Core i3 or equivalent
• Memory: 4 GB RAM
• Storage: 10 GB available space
iv | Preface

Software Requirements
You'll also need the following software installed in advance:
• OS: Any desktop Linux version or macOS, or Windows 7, 8.1, or 10
• For Windows 10 systems: Windows subsystem for Linux (this is only available in
the latest versions)
• Browser: Use one of the latest browsers, such as Firefox, Chrome, Safari, Edge, or
IE11
• Modern C++ compiler

Additional Resources
The code bundle for this book is also hosted on GitHub at https://github.com/
TrainingByPackt/Cpp-Fundamentals.
We also have other code bundles from our rich catalog of books and videos available at
https://github.com/PacktPublishing/. Check them out!

Conventions
Code words in text, database table names, folder names, filenames, file extensions,
pathnames, dummy URLs, user input, and Twitter handles are shown as follows: "Create
a file named HelloUniverse.cpp and save it."
A block of code is set as follows:
#include <iostream>
int main() {
std::cout << "Hello Universe" << std::endl;
return 0;
}
New terms and important words are shown in bold. Words that you see on the screen,
for example, in menus or dialog boxes, appear in the text like this: "Select System info
from the Administration panel."
Getting Started
1
Lesson Objectives

By the end of this chapter, you will be able:

• Explain the C++ compilation model

• Execute the main() function

• Illustrate the declaration and definition of variables

• Determine built-in arithmetic types, references, and pointers

• Explain the scope of a variable

• Use control flow statements

• Define and utilize arrays

In this chapter, you will learn about the usage of variables and control flow statements to create
more robust programs.
2 | Getting Started

Introduction
C++ has been a major player in the software development industry for more than 30
years, supporting some of the most successful companies in the world.
In recent years, interest in the language has been growing more than ever, and it is an
extremely popular choice for large-scale systems, with many big companies sponsoring
its advancement.
C++ remains a complex language, which puts a lot of power in the hands of the
developer. However, this also comes with a lot of opportunities to make mistakes. It
is a unique language as it has the ability to enable programmers to write high-level
abstractions while retaining full control of hardware, performance, and maintainability.

The C++ Compilation Model


It is fundamental to know how C++ compilation works to understand how programs
are compiled and executed. Compiling C++ source code into machine-readable code
consists of the following four processes:
1. Preprocessing the source code.
2. Compiling the source code.
3. Assembling the compiled file.
4. Linking the object code file to create an executable file.

Let's start with a simple C++ program to understand how compilation happens.
Create a file named HelloUniverse.cpp and save it on the Desktop after copy-pasting
the following code:
#include <iostream>
int main(){
// This is a single line comment
/* This is a multi-line
comment */
std::cout << "Hello Universe" << std::endl;
return 0;
}
The C++ Compilation Model | 3

Now, using the cd command on the Terminal, navigate to the location where our file is
saved and execute the following command if you are on UNIX:
> g++ -o HelloUniverse HelloUniverse.cpp
> ./HelloUniverse
If you are on a Windows system, a different compiler must be used. The command to
compile the code with the Visual Studio compiler is as follows:
> cl /EHsc HelloUniverse.cpp
> HelloUniverse.exe
This program, once executed, will print Hello Universe on the Terminal.
Let's demystify the C++ compilation process using the following diagram:

Figure 1.1: C++ compilation of the HelloUniverse file


4 | Getting Started

1. When the C++ preprocessor encounters the #include <file> directive, it replaces
it with the content of the file creating an expanded source code file.
2. Then, this expanded source code file is compiled into an assembly language for
the platform.
3. The assembler converts the file that's generated by the compiler into the object
code file.
4. This object code file is linked together with the object code files for any library
functions to produce an executable file.

Difference Between Header and Source Files


Source files contain the actual implementation code. Source files typically have
the extension .cpp, although other extensions such as .cc, .ccx, or .c++ are also quite
common.
On the other hand, header files contain code that describes the functionalities that
are available. These functionalities can be referred to and used by the executable code
in the source files, allowing source files to know what functionality is defined in other
source files. The most common extensions for header files are .hpp, .hxx, and .h.
To create an executable file from the header and the source files, the compiler starts
by preprocessing the directives (preceded by a # sign and generally at the top of the
files) that are contained in them. In the preceding HelloUniverse program, the directive
would be #include. It is preprocessed by the compiler before actual compilation
and replaced with the content of the iostream header, which describes standard
functionality for reading and writing from streams.
The second step is to process each source file and produce an object file that contains
the machine code relative to that source file. Finally, the compilers link all the object
files into a single executable program.
We saw that the preprocessor converts the content of the directives into the source
files. Headers can also include other headers, which will be expanded, creating a chain
of expansions.
For example, let's assume that the content of the logger.hpp header is as follows:
// implementation of logger

Let's also assume that the content of the calculator.hpp header is as follows:
#include <logger.hpp>
// implementation of calculator
The C++ Compilation Model | 5

In the main.cpp file, we include both directives, as shown in the following code snippet:
#include <logger.hpp>
#include <calculator.hpp>

int main() {
// use both the logger and the calculator
}

The result of the expansion will be as follows:


// implementation of logger
// implementation of logger
// implementation of calculator
int main() {
// use both the logger and the calculator
}

As we can see, the logger has been added in the resulting file twice:
• It was added the first time because we included logger.hpp in the main.cpp file
• It was added the second time because we included calculator.hpp, which then
includes logger.hpp

Included files that are not directly specified in a #include directive in the file we are
compiling, but are instead included by some other included file, are called transitive
included files.
Often, including the same header file multiple times creates a problem with multiple
definitions, as we will see in Lesson 2, Functions, and the Lesson 03, Classes.
Including the same file multiple times is very likely because of the transitive included
files we explained before, and will often result in a compilation error. In C++, there is
a convention to prevent problems that originate from including a header file multiple
times: include guards.
An include guard is a specific pattern of instructing the preprocessor to ignore the
content of the header if it has been included before.
6 | Getting Started

It consists of writing all the header code inside the following structure:
#ifndef <unique_name>
#define <unique_name>
// all the header code should go here
#endif /* <unique_name> */

Here, <unique_name> is a name unique throughout the C++ project; it typically consists of
the header file name, such as LOGGER_HPP for the logger.hpp header.
The preceding code checks whether a special preprocessor variable, <unique_name>,
exists. If it does not exist, it defines it and it proceeds to read the content of the header.
If it exists, it will skip all the code until the #endif part.
Since initially the special variable does not exist, the first time the preprocessor
includes a header, it creates the variable and proceeds to read the file. The subsequent
times, the variable is already defined, so the preprocessor jumps to the #endif directive,
skipping all the content of the header file.
Compilation is a process that ensures that a program is syntactically correct, but it does
not perform any checks regarding its logical correctness. This means that a program
that compiles correctly might still produce undesired results:

Figure 1.2: Compilation and linking processes for an executable file


The C++ Compilation Model | 7

Every C++ program needs to define a starting point, that is, the part of the code the
execution should start from. The convention is to have a uniquely named main function
in the source code, which will be the first thing to be executed. This function is called
by the operating system, so it needs to return a value that indicates the status of the
program; for this reason, it is also referred to as
the exit status code.
Let's see how we can compile a program.
Together with C, C++ is the language with the most supported hardware and platforms.
This means that there are many C++ compilers, produced by many different vendors.
Each compiler can accept parameters in a different way, and it's important to consult
the manual of the compiler you are using when developing in C++ to understand the
available options and their meaning.
We'll now see how to compile a program with two of the most common compilers: the
Microsoft Visual Studio compiler and GCC.

Compiling a File into an Object File


To compile the myfile.cpp file in to an object file named myfile.obj, we can run the
following commands:

Figure 1.3: Compiling the CPP file

When we compile, it is common to include some headers.


We can include the headers defined in the C++ standard without performing any action,
but in case we want to include user-defined headers, we need to tell the compiler in
which folders to look up the header files.
For MSVC, you need to pass the parameter as /I path, where path is the path to the
directory to look in for the header.
For GCC, you need to pass the parameter as -I path, where path has the same meaning
as in MSVC.
8 | Getting Started

If myfile.cpp is including a header in the include directory, we would compile the file
with the following commands:

Figure 1.4: Compiling the CPP file with the include directory

We can compile several files in their respective object files, and then link them all
together to create the final application.

Linking Object Files


To link together two object files called main.obj and mylib.obj into an executable, we
can run the following commands:

Figure 1.5: Compiling two object files

With MSVC, we will create an executable named main.exe, while with g++, the
executable will be named main.
For convenience, MSVC and GCC offer a way to compile several files into an executable,
without the need to create an object file for each file, and then link the files together.
Even in this case, if the files are including any user-defined header, you need to specify
the header location with the /I or -I flags.
The C++ Compilation Model | 9

To compile the main.cpp and mylib.cpp files together, which uses some headers from
the include folder, you can use the following commands:

Figure 1.6: Compiling files with include folder

Working with the main Function


In the next chapter, we will discuss functions in more depth; for now, we can define the
main function, which does nothing, apart from returning a successful status code in the
following way:
int main()
{
return 0;
}

The first line contains the definition of the function, constituted by the return type int,
the name of the main function, and the list of arguments, which in this case is an empty
list. Then, we have the body of the function, delimited by curly braces. Finally, the body
is composed of a single instruction that will return a successful status code.

Note
As opposed to C, in a C++ program, the return statement is optional. The compiler
automatically adds return 0 if you don't explicitly return a value.
10 | Getting Started

We will discuss these topics in more detail later; what is important to know is that this
is a valid C++ program that can be compiled and executed.

Note
Most C compilers can compile C or C++ by determining the language based on the
file extension.

Exercise 1: Compiling and Executing the main Function


In this exercise, we will create a source file named main.cpp containing the code.
Compile the file and run the program. We will be using it to explore the C++
environment:
1. Using your favorite text editor (Sublime Text, Visual Studio Code, Atom, or
Notepad++ if you use Windows), create a new file and name it main.cpp.
2. Write the following code in the main.cpp file and save it:
int main()
{
return 0;
}
3. Compile the main.cpp file using the following command:
//On UNIX:
> g++ main.cpp

//On Windows:
> cl /EHsc main.cpp
4. The compilation process will produce an executable file, which will be named
main.exe on a Windows system and main.out on a UNIX one.
Built-in Data Types | 11

Built-in Data Types


In most programming languages, data is stored in variables, which are labels that refer
to the part of memory defined by the programmer. Each variable has an associated type.
The type defines what kind of values the variable can hold.
The built-in data types of C++ are divided into two categories:
• Primitive data types: Can be used directly by the user to declare variables
• Abstract or user defined data types: Are defined by the user, for example, to
define a class in C++ or a structure

Primitive Data Types


Primitive data types consist of the following types:
• Integer: The int type stores a whole number value ranging from -2147483648 to
2147483647. This data type usually takes up 4 bytes of memory space.
• Character: The char type stores character data. It is guaranteed to be big enough
to represent any UTF-8 single byte code unit; for UTF-16 and UTF-32, char16_t
and char32_t are used, respectively. char typically takes 1 byte of memory space.
• Boolean: The bool data type is capable of holding one of two values: true
or false.
• Floating-point: The float type is used for storing single precision floating point
values. This data type usually takes up 4 bytes of memory space.
• Double floating point: The double type is used for storing double precision floating
point values. This data type usually takes up 8 bytes of memory space.
• Void: The void type is a valueless data type that is used for functions that do not
return a value.
• Wide character: The wchar_t type is also used to represent character sets, but
allows for greater size. While char supports characters between 8 and 32 bits, a
wide character is 2 to 4 bytes long.

The character types char and wchar_t hold numeric values corresponding to the
characters in the machine's character set.
12 | Getting Started

Datatype Modifiers
The numeric types offered by the C++ programming language fall into three categories:
• Signed
• Unsigned
• Floating point

The signed and unsigned types come with different sizes, which means each of them
can represent a smaller or larger range of values.
Integer types can be signed or unsigned, where signed types can be used to distinguish
between negative or positive numbers, while unsigned can only represent numbers
greater than or equal to zero.
The signed keyword is optional; the programmer only needs to specify it if the type
is unsigned. Thus, signed int and int are the same types, but they are different from
unsigned int, or just unsigned for brevity. Indeed, if it is not specified, an unsigned type
always defaults to int.
Integers, as previously mentioned, can come in different sizes:
• int
• short int
• long int
• long long int

The short int type, or just short, is guaranteed to be at least 16 bits according to the
standard. This means it can hold values in the range of -32768 to 32767. If it was also
unsigned, so unsigned short int or just unsigned int, this range would be 0 to 65535.

Note
The effective size in memory of types can change based on the platform for which
the code is compiled. C++ is present in many platforms, from supercomputers
in data centers to small embedded chips in industrial settings. To be able to
support all these different types of machines, the standard only sets the minimum
requirements on built-in types.
Built-in Data Types | 13

Variable Definition
A variable is named storage that refers to a location in memory that can be used to hold
a value. C++ is a strongly-typed language and it requires every variable to be declared
with its type before its first use.
The type of the variable is used by the compiler to determine the memory that needs to
be reserved and the way to interpret its value.
The following syntax is used to declare a new variable:
type variable_name;

Variable names in C++ can contain letters from the alphabet, both upper and lower case,
digits and underscores (_). While digits are allowed, they cannot be the first character
of a variable name. Multiple variables of the same type can all be declared in the same
statement by listing their variable names, separated by commas:
type variable_name1, variable_name2, …;

This is equivalent to the following:


type variable_name1;
type variable_name2;
type ...;
When declaring a variable, its value is left undetermined until an assignment is
performed. It is also possible to declare a variable with a given value; this operation is
also referred to as variable initialization.
One way – and probably the most common one – to initialize a variable, also referred to
as C-like initialization, uses the following syntax:
type variable_name = value;

Another solution is constructor initialization, which we will see in detail in Lesson 3,


Classes. Constructor initialization looks like this:
type variable_name (value);

Uniform initialization or list initialization introduces brace initialization, which allows


for the initialization of variables and objects of different types:
type variable_name {value};
14 | Getting Started

Demystifying Variable Initialization


When a variable is initialized, the compiler can figure out the type needed to store the
value provided, which means that it is not necessary to specify the type of the variable.
The compiler is indeed able to deduct the type of the variable, so this feature is also
referred to as type deduction. For this reason, the auto keyword has been introduced to
replace the type name during initialization. The initialization syntax becomes this:
auto vvariable_name = value;

Another way to avoid directly providing a type is to use the decltype specifier. It is used
to deduce a type of a given entity and is written with the following syntax:
type variable_name1;
decltype(variable_name1) variable_name2;
Here, variable_name2 is declared according to the type deducted from
variable_name1.

Note
Type deduction using the auto and decltype keywords has been introduced by the
C++11 standard to simplify and facilitate variable declaration when the type cannot
be obtained. But at the same time, their extended use when not really needed can
reduce code readability and robustness. We will see this in more detail in Lesson 4,
Generic Programming and Templates.

In the following code, we will check a valid statement for variables by creating a new
source file named main.cpp and analyzing the code one line at a time.
Which one of the following is a valid statement?
int foo;
auto foo2;
int bar = 10;
sum = 0;
float price = 5.3 , cost = 10.1;
auto val = 5.6;
auto val = 5.6f;
auto var = val;
int a = 0, b = {1} , c(0);
Pointers and References | 15

Pointers and References


In the previous section, variables have been defined as portions of memory that can be
accessed by their name. In this way, the programmer does not need to remember the
memory location and size that's reserved, but can conveniently refer to the variable
name.
In C++, the way to retrieve the actual memory address of a variable is done by preceding
the variable name with an ampersand sign (&), also known as the address-of operator.
The syntax to use the concept of the address-of operator is as follows:
&variable_name

Using this in code will return the physical memory address of the variable.

Pointers
A data structure that's capable of storing a memory address in C++ is known as a
pointer. A pointer always points to an object of a specific type, and because of that we
need to specify the type of the object that's pointed to when declaring the pointer.
The syntax to declare a pointer is as follows:
type * pointer_name;

Multiple declarations in the same statement are also possible when it comes to a
pointer, but it is important to remember that an asterisk (*) is needed for each pointer
declaration. An example of multiple pointer declaration is as follows:
type * pointer_name1, * pointer_name2, *...;

When the asterisk is specified only for the first declaration, the two variables will have
different types. For example, in the following declaration, only the former is a pointer:
type * pointer_name, pointer_name;

Note
Independently of the pointed variable type, a pointer will always occupy the same
size in memory. This derives from the fact that the memory space needed by the
pointer is not related to a value stored by the variable, but to a memory address
that is platform-dependent.
16 | Getting Started

Intuitively, a pointer assignment has the same syntax as any other variable:
pointer_name = &variable_name;

The previous syntax will copy the memory address of the variable_name variable into
the pointer named pointer_name.
The following code snippet will first initialize pointer_name1 with the memory address
of variable_name, and then it initializes pointer_name2 with the value stored in pointer_
name1, which is the memory address of variable_name. As a result, pointer_name2 will end
up pointing to the variable_name variable:
type * pointer_name1 = &variable_name;
type * pointer_name2 = pointer_name1;
The following implementation is invalid:
type * pointer_name1 = &variable_name;
type * pointer_name2 = &pointer_name1;
This time, pointer_name2 would be initialized with the memory address of
pointer_name1, resulting in a pointer that points to another pointer. The way
to point a pointer to another pointer is to use the following code:
type ** pointer_name;

Two asterisks (*) indicate the type that's pointed is now a pointer. In general, the syntax
simply requires an asterisk (*) for each level of indirection in the declaration of the
pointer.
To access the actual content at a given memory address, it is possible to use the
dereference operator (*), followed by the memory address or a pointer:
type variable_name1 = value;
type * pointer_name = &variable_name1;
type variable_name2 = *pointer_name;
The value contained by variable_name2 is the same as the one contained by variable_
name1. The same applies when it comes to assignment:
type variable_name1 = value1;
type * pointer_name = &variable_name1;
*pointer_name = value2;
Pointers and References | 17

References
Unlike a pointer, a reference is just an alias for an object, which is essentially a way to
give another name to an existing variable. The way to define a reference is as follows:
type variable_name = value;
type &reference_name = variable_name;
Let's examine the following example:
#include <iostream>
int main()
{
int first_variable = 10;
int &ref_name = first_variable;
std::cout << "Value of first_variable: " << first_variable << std::endl;
std::cout << "Value of ref_name: " << ref_name << std::endl;
}
//Output
Value of first_variable: 10
Value of ref_name: 10
We can identify three main differences with pointers:
• Once initialized, a reference remains bound to its initial object. So, it is not
possible to reassign a reference to another object. Any operations performed on a
reference are actually operations on the object that has been referred.
• Since there is not the possibility to rebind a reference, it is necessary to initialize
it.
• References are always associated with a variable that's stored in memory, but the
variable might not be valid, in which case the reference should not be used. We
will see more on this in the Lesson 6, Object-Oriented Programming.

It is possible to define multiple references to the same object. Since the reference is not
an object, it is not possible to have a reference to another reference.
18 | Getting Started

In the following code, given that a is an integer, b is a float, and p is a pointer to an


integer, verify which of the variable initialization is valid and invalid:
int &c = a;
float &c = &b;
int &c;
int *c;
int *c = p;
int *c = &p;
int *c = a;
int *c = &b;
int *c = *p;

The const Qualifier


In C++, it is possible to define a variable whose value will not be modified once
initialized. The way to inform the compiler of this situation is through the const
keyword. The syntax to declare and initialize a const variable is as follows:
const type variable_name = value;

There are several reasons to enforce immutability in a C++ program, the most important
ones being correctness and performance. Ensuring that a variable is constant will
prevent the compilation of code that accidentally tries to change that variable,
preventing possible bugs.
The other reason is that informing the compiler about the immutability of the variable
allows for optimizing the code and logic behind the implementation of the code.

Note
After creating an object, if its state remains unchanged, then this characteristic is
known as immutability.
Pointers and References | 19

An example of immutability is as follows:


#include <iostream>
int main()
{
const int imm = 10;
std::cout << imm << std::endl;
//Output: 10
int imm_change = 11;
std::cout << imm_change << std::endl;
//Output: 11
imm = imm_change;
std::cout << imm << std::endl;
//Error: We cannot change the value of imm
}
An object is immutable if its state doesn't change once the object has been created.
Consequently, a class is immutable if its instances are immutable. We will learn more
about classes in Lesson 3, Classes.
Modern C++ supports another notion of immutability, which is expressed with the
constexpr keyword. In particular, it is used when it is necessary for the compiler to
evaluate the constant at compile time. Also, every variable declared as constexpr is
implicitly const.
The previous topic introduced pointers and references; it turns out that even those
can be declared as const. The following is pretty straightforward to understand, and its
syntax is as follows:
const type variable_name;
const type &reference_name = variable_name;
This syntax shows how we can declare a reference to an object that has a const type;
such a reference is colloquially called a const reference.
20 | Getting Started

References to const cannot be used to change the object they refer to. Note that it
is possible to bind a const reference to a non-const type, which is typically used to
express that the object that's been referenced will be used as an immutable one:
type variable_name;
const type &reference_name = variable_name;
However, the opposite is not allowed. If an object is const, then it can only be
referenced by a const reference:
const type variable_name = value;
type &reference_name = variable_name;
// Wrong
An example of this is as follows:
#include <iostream>
int main()
{
const int const_v = 10;
int &const_ref = const_v;
//Error
std::cout << const_v << std::endl;
//Output: 10
}

Just like for references, pointers can point to the const object, and the syntax is also
similar and intuitive:
const type *pointer_name = &variable_name;

An example of this is as follows:


#include <iostream>
int main()
{
int v = 10;
Pointers and References | 21

const int *const_v_pointer = &v;


std::cout << v << std::endl;
//Output: 10
std::cout << const_v_pointer << std::endl;
//Output: Memory location of v
}

const object addresses can only be stored in a pointer to const, but the opposite is not
true. We could have a pointer to const point to a non-const object and, in this case, like
for a reference to const, we are not guaranteed that the object itself will not change, but
only that the pointer cannot be used to modify it.
With pointers, since they are also objects, we have an additional case, which is the const
pointer. While for references saying const reference is just a short version of reference
to const, this is not the case for the pointer and has a totally different meaning.
Indeed, a const pointer is a pointer that is itself constant. Here, the pointer does not
indicate anything about the pointed object; it might be either const or non-const, but
what we cannot change instead is the address pointed to by the pointer once it has
been initialized. The syntax is as follows:
type *const pointer_name = &variable_name;

As you can see, the const keyword is placed after the * symbol. The easiest way to keep
this rule in mind is to read from right to left, so pointer-name > const > * > type can
be read as follows: pointer-name is a const pointer to an object of type type. An example
of this is as follows:
#include <iostream>
int main()
{
int v = 10;
int *const v_const_pointer = &v;
std::cout << v << std::endl;
22 | Getting Started

//Output: 10
std::cout << v_const_pointer << std::endl;
//Output: Memory location of v
}

Note
Pointer to const and const to pointer are independent and can be expressed in the
same statement:

const type *const pointer_name = &variable_name;

The preceding line indicates that both the pointed object and the pointer
are const.

The Scope of Variables


As we have already seen, variable names refer to a specific entity of a program. The live
area of the program where this name has a particular meaning is also called a scope of a
name. Scopes in C++ are delimited with curly braces, and this area is also called a block.
An entity that's declared outside of any block has a global scope and is valid anywhere
in the code:

Figure 1.7: Scope of a variable

The same name can be declared in two scopes and refers to different entities. Also, a
name is visible once it is declared until the end of the block in which it is declared.
Pointers and References | 23

Let's understand the scope of global and local variables with the following example:
#include <iostream>
int global_var = 100;
//Global variable initialized

int print(){
std::cout << global_var << std::endl;
//Output: 100
std::cout << local_var << std::endl;
//Output: Error: Out of scope
}
int main()
{
int local_var = 10;
std::cout << local_var << std::endl;
//Output: 10
std::cout << global_var << std::endl;
//Output: 100
print();
//Output:100
//Output: Error: Out of scope
}

Scopes can be nested, and we call the containing and contained scope the outer and
inner scope, respectively. Names declared in the outer scope can be used in the inner
one. Re-declaration of a name that was initially declared in the outer scope is possible.
The result will be that the new variable will hide the one that was declared in the outer
scope.
Let's examine the following code:
#include <iostream>
int global_var = 1000;

int main()
24 | Getting Started

{
int global_var = 100;
std::cout << "Global: "<< ::global_var << std::endl;
std::cout << "Local: " << global_var << std::endl;
}
Output:
Global: 1000
Local: 100
In the next chapter, we will explore how to use local and global variables with functions.
In the following code, we will find the values of all the variables without executing the
program.
The following program shows how variable initialization works:
#include <iostream>
int main()
{
int a = 10;
{
int b = a;
}
const int c = 11;
int d = c;
c = a;
}

Control Flow Statements


In a program, it is rare to provide useful functionality by just executing a linear
sequence of operations. Commonly, a program must be able to react in different ways
in response to different situations or execute the same operation multiple times in
different contexts.
We will now see the control flow statements C++ offers to the programmer to control
the sequence of operations to be executed.
Control Flow Statements | 25

Selection Statement – if-else


C++ provides conditional execution support, where the if keyword indicates whether
or not to execute the following statement or block, depending on the fulfillment of the
condition provided:
if (condition) statement

If the expression named condition evaluates to true, then the statement is


executed; otherwise, it will be ignored and the program will continue with the
subsequent code.
The conditionally executed code can be a single statement or an entire block containing
multiple ones. These statements needs to be enclosed in braces ({}) to form a block:
if (condition) {
statement_1;
statement_2;
statement_N;
}

Note
It is common to forget the brace brackets and write the control statement in the
following manner:

if (condition)

statement1

statement2

In such a case, the compiler will not warn you, and it will execute statement1
depending on the condition, but always execute statement2. To avoid such a
situation, it can be a good practice to always add the braces.
26 | Getting Started

It is possible to specify what to execute instead when the condition evaluates to false.
This is done through the else keyword, which is followed by a statement or a block.
The following syntax is used to indicate that statement1 should be executed if the case
condition evaluates to true, and otherwise statement2 is executed:
if (condition) statement1 else statement2

Finally, we can concatenate on multiple if-else statements to produce a more complex


branching logic. Let's examine the following example:
if (condition1) {
statement1
} else if (condition2) {
statement2
} else {
statement3
}

With this generic structure, it is possible to check the unlimited number of conditions
and execute only the corresponding statement or the final one contained in the else
branch.
It is important to be aware that once one of the conditions is met, all of the ones that
follow are discarded, for example:
if (x > 0) {
// When x is greater than 0, statement1 is executed.
// If that is not the case, the control jumps to the else block.
statement1
} else if (x > 100) {
statement2
}

The previous code will always execute statement1 for any positive x, regardless of
whether it is greater than 100 or not.
Control Flow Statements | 27

An alternative way is to sequence several if keywords, as follows:


if (condition1)
// If condition1 is true, statement1 is executed
statement1
if (condition2)
// if condition2 is true then statement2 is executed
statement2
/* independently whether condition1 and condition2 is true or not, the
statement3 will be executed */
statement3
Let's demystify the previous logic with the following example:
#include <iostream>
int main()
{
int x = 10;
if (x > 0){
std::cout << x << std::endl;
}
if (x > 11 ){
std::cout << x << std::endl;
}
else{
std::cout << x-1 << std::endl;
}
}

Output:
10
9
28 | Getting Started

In this way, all the conditions are evaluated independently and more than one
statement can potentially be executed.

Note
As the else statement has no condition defined, after evaluating the if statement,
the control comes to the else block to execute the statement.

Selection Statement – switch


Another selection statement, with similarities to the if-else concatenation
construction, is the switch statement. It is limited to constant expressions and is mainly
used to check for a value among a number of possible expressions:
switch (expression)
{
case constant1:
group-of-statements-1;
break;

case constant2:
group-of-statements-2;
break;
...
default:
default-group-of-statements;
break;
}
Control Flow Statements | 29

The expression present in the parentheses following the switch keyword is evaluated
against multiple cases, searching for the first equality between the expression and the
constants. If none of the cases match, the default one (if it exists, since it is optional) is
executed.
It is important to keep in mind that the order of evaluation is sequential, and as soon
as one of the constants matches, the corresponding group of statements are executed.
The break keyword prevents them from further execution. If the break keyword is not
included, all statements following the case, including the ones under different labels,
are also executed.
We will explore the break keyword more in the Jump statements – break and continue
section.

Iteration Statement – for loop


The for loop is a construct that's used to repeat a statement a certain number of times.
The syntax of the for loop is as follows:
for (initialization; condition; increase){
statement1;
statement2;
...
statementN;
}

The for loop consists of two parts: the header and the body. The former controls how
many times the latter is repeated. The header is the part enclosed by parentheses and
it is formed by initialization, condition, and increase statements. The body can be a
single statement or a block of multiple ones.
The initialization statement is typically (but not necessarily) used to declare a new
variable, usually a counter, and to initialize it to a certain value. The initialization
statement is executed only once, at the beginning of the loop.
Secondly, the condition statement is checked. This is similar to the condition that's
checked for an if statement. If the condition is true, the body of the loop is executed,
otherwise the program continues its execution with the instruction after the body of
the for loop.
30 | Getting Started

After the body executes, the increase statement is executed. This usually changes the
counter of the initialization statement. The condition is then checked again and, if true,
the steps are repeated. The loop ends when the condition evaluates to false.
The fields in the header of a for loop are optional and can be left blank, but the
semicolons cannot be omitted. When the condition is omitted, it always evaluates to
true. For example, the following corresponds to an infinite loop where the statement is
executed unconditionally:
for ( ; ; ) statement;

Another variant of the for loop is called a range-based for loop, the syntax for which is
as follows:
for ( declaration : range ) statement;

A range is a sequence of elements, like arrays, which are explained in the next section.
This range-based for loop is used to iterate over all elements of these sequences. The
range is the name of the sequence, and in the for declaration, the name is a temporary
variable that's declared for every iteration of the loop. This is used to store the current
element. The declaration needs to be the same type as the elements contained in the
range.

Note
A range-based for loop is a good example where type deduction and the use of
the auto keyword for the declaration makes the code more readable and helps the
programmer find the right type to use.
Control Flow Statements | 31

A loop placed inside a loop is known as a nested loop. Let's look at the following
diagram to understand what a nested for loop is:

Figure 1.8: Nested for loop

Using the following example, let's explore how a nested for loop works and print a
reverse half-triangle on the console:
#include <iostream>
int main()
{
for (int x = 0; x < 5; x++){
for (int y = 5; y > x; y--){
std::cout << "*";
}
std::cout <<"\n" ;
}
}
Output:
*****
****
32 | Getting Started

***
**
*

Iteration Statement – while loop


Another iteration statement is the while loop. It is simpler than the for loop. The syntax
for it is as follows:
while (condition) statement;

It repeats the statement until the condition is met. When the condition is not true
anymore, the loop ends and the program continues its execution right after the loop:

Note
A while loop can always be expressed using a for loop.

Here is an example: for ( ; condition ; ) statement;

Iteration Statement – do-while loop


A similar loop is the do-while loop, where the condition is checked after the execution
of the statement, instead of before. It uses the following syntax:
do statement while (condition);

It guarantees at least one execution of the statement, even though the condition never
evaluates to true.

Jump Statements – break and continue


The break keyword is used to end a loop independently, regardless of whether it fulfils
its condition. In the following program, when condition2 becomes true, the break
statement will immediately terminate the while loop:
while (condition1){
statement1;
if (condition2)
break;
}
The try-catch block | 33

Alternatively, the continue statement is used to skip the rest of the body's loop in the
current iteration. In the following example, when condition2 evaluates to true, continue
is called, causing the program to reach the end of the loop, skipping statement2 and
continuing with the next iteration:
while (condition1){
statement1;
if (condition2)
continue;
statement2;
}

Note
The break and continue statements can be used in both for and while loops.

The try-catch block


During the execution of a program, an anomaly may occur. We refer to these
runtime problems as exceptions, and they represent the response to an exceptional
circumstance that arises outside of the normal functioning of a program. Designing
code that's resilient to errors is one of the hardest things a programmer has to deal
with.
Exceptions are generally thrown using the throw keyword when something that cannot
be handled is encountered by the program. This is also referred to as raising an
exception.
The try keyword is followed by a block containing statements that might throw one or
more exceptions. These exceptions can be caught by one or more catch clauses, which
are sequentially listed after the try block. The syntax for this is as follows:
try {
statement1;
} catch (exception-declaration1) {
statement2;
} catch (exception-declaration2) {
statement3;
}
...
34 | Getting Started

A catch block consists of the catch keyword, the exception declaration, and a block.
Based on the exception thrown inside the try block, one catch clause is selected and
the corresponding block is executed. Once the catch block has terminated, the program
continues its execution with the statement following the last catch clause.
Let's examine the following example to understand how try-catch conditional
statements handle exceptions:
#include <iostream>
int main()
{
int x = 10;
try {
std::cout << "Inside try block" << std::endl;
if (x > 0) // True
{
throw x;// Following statement will be skipped
std::cout << "After throw keyword" << std::endl;
}
}
catch (int x ) {
std::cout << "Inside catch block: Exception found" << std::endl;
}
std::cout << "Outside try-catch block" << std::endl;
}
Output:
Inside try block
Inside catch block: Exception found
Outside try-catch block
The try-catch block | 35

Exercise 2: Counting the Number of Times a Specific Number Appears in a


Given List
In this exercise, we will discuss using the if statement and a for loop to count our
magic number. Here, we will be trying to find all numbers that are divisible by 3, ranging
from 1 to 30.

Hint
To find out if a number is divisible by another, use the modulo (%) operator.

Now, let's perform the following steps:


1. Import all the required header files:
#include <iostream>
2. We need to store the number of times a number is divisible by 3 in a counter. For
this reason, we define and initialize the count variable to 0:
unsigned count = 0;
3. Now, we will use a for loop that produces values from 1 to 30 so that we can check
whether they are divisible by 3 or not:
for(unsigned x = 1; x <= 30; x++){
}
4. Finally, we will check in the body of the for loop by using an if statement and the
expression x%3 == 0, which evaluates to true if the division has a remainder equal
to 0:
if (x%3 == 0) {
count++;
}
5. If the previous condition returns to true, then the X variable is divisible by 3 and
we can increment the counter.
6. Finally, we can print count:
std::cout << count << std::endl;
36 | Getting Started

Bonus exercises:
• Find how many numbers are divisible by 11 within the range of 1 to 100
• Print all the numbers that are not divisible by 3 within the range of 1 to 30

Activity 1: Finding the Factors of 7 between 1 and 100 Using a while Loop
In the following activity, we will use a while loop and implement the previous concept
from the earlier exercise to print the numbers between 1 and 100 that are divisible by 7.
Now, let's rewrite the previous code using a while loop in the following way:
1. Create a variable of the unsigned type.
2. Now, write the logic to print the numbers that are divisible by 7 using
the while loop.
3. Then, we have to increase the value of i after each iteration.
Use the following code:
i++;
The solution for this activity can be found on page 282.

Arrays
An array is a data structure containing a series of elements of the same type that have
been placed in contiguous memory locations that can be individually accessed by their
position.
Arrays have fixed sizes and cannot be extended; this contributes to their runtime
performance, with a cost in terms of limited flexibility.

Array Declaration
Like any other variable, arrays need to be declared before they can be used. An array
declaration has the following form:
type name [elements];

Here, type is the type of the contained elements, name is the identifier of the array
variable, and elements is the length of the array, so it signifies the number of elements
contained within.
Arrays | 37

The term elements needs to be a constant expressions that is known at compile time,
since that is the time when the array size is evaluated to determine the dimension of
the block of static memory to allocate.
When an array is declared, its content is left undetermined, which means that the
elements are not set to any specific value. This is often confusing for programmers as
you might expect that the elements are initialized to the default value for the array type.

Array Initialization
Array elements can be specifically initialized at declaration time by enclosing these
initial values in braces:
int foo [5] = { 1, 2, 11, 15, 1989 };

When we initialize a list array, we can also omit its length as it will be determined by the
number of values provided. The following declaration is equivalent to the previous one:
int foo [] = { 1, 2, 11, 15, 1989 };

If the number of elements is provided, but the array is initialized with fewer elements,
then the remaining value will be zero-initialized, for example:
int foo [5] = { 1, 2, 11 };

The previous code is equivalent to the following:


int foo [5] = { 1, 2, 11, 0, 0 };

Accessing the Values of an Array


The values of an array can be accessed in the same way as any other values of the same
type. The following is the syntax to access an array:
name[index]

An element of an array can be accessed to store a new element or to read its value.
For example, the following statement updates the value at position 4 of the previously
declared array named foo:
foo [4] = 15

The following is used to copy the content of the element at position 2 into a new
variable:
int x = foo [2]
38 | Getting Started

It is important to notice that the elements at positions 4 and 2 refer to the fifth and
third elements, respectively. This is due to the fact that indexing starts from 0. The
following diagram illustrates how index entries work in arrays:

Figure 1.9: Initializing a one-dimensional array

Exceeding the valid range of indices for an array is syntactically correct, so the compiler
will not produce any errors. Accessing an array out of bounds in C++ is considered an
undefined behavior, which means that the code's behavior is not prescribed by the
language specification. This can result in runtime errors such as bugs caused by access
to an unallocated location in memory or program termination (segmentation fault) due
to an attempt to access memory not owned by the program.

Multidimensional Arrays
Multidimensional arrays are commonly described as arrays of arrays, where an array's
elements are other arrays.
The following syntax illustrates a bi-dimensional array:
type name [n][m];
int bi_array [3][4]
Here, n is the dimension of the array and m is the dimension of its elements.
Typically, in a bi-dimensional array like the previous one, the first dimension is referred
to as the row and the second one is referred to as the column.
Multidimensional arrays are not limited to two dimensions; they can have as
many dimensions as needed, but keep in mind that the memory that's used
increases exponentially with each dimension. Similar to one-dimensional arrays,
multidimensional arrays can be initialized by specifying a list of initializers, one for each
row. Let's examine the following code:
#include <iostream>
int main()
{
Arrays | 39

int foo [3][5] = {{ 1, 2, 11, 15, 1989 }, { 0, 7, 1, 5, 19 }, { 9, 6, 131,


1, 2 }};
for (int x = 0; x < 3; x++)
{
for (int y = 0; y < 5; y++)
{
std::cout <<"Array element at [" << x << "]" << "[" << y << "]: "<<
foo[x][y] << std::endl;
}
}
}
Output:
Array element at [0][0]: 1
Array element at [0][1]: 2
Array element at [0][2]: 11
Array element at [0][3]: 15
Array element at [0][4]: 1989
Array element at [1][0]: 0
Array element at [1][1]: 7
Array element at [1][2]: 1
Array element at [1][3]: 5
Array element at [1][4]: 19
Array element at [2][0]: 9
Array element at [2][1]: 6
Array element at [2][2]: 131
Array element at [2][3]: 1
Array element at [2][4]: 2
Alternatively, since the compiler can infer the length of the internal arrays from the
definition, the nested braces are optional and provided only for readability:
int foo [3][5] = {1, 2, 11, 15, 1989, 0, 7, 1, 5, 19, 9, 6, 131, 1, 2};
40 | Getting Started

Activity 2: Defining a Bi-Dimensional Array and Initializing Its Elements


In this section, we will define a bi-dimensional array (3x3) of type integer and write a
program to assign each element the addition of their corresponding index entries in the
array:
1. Define an array of integers of size 3x3.
2. Iterate over each element of the array using a nested for loop and assign the
product values x and y to the index.

Note:
The solution for this activity can be found on page 282.

Summary
In this chapter, we saw the basic structure and syntax of the language. We started with
an overview of the compilation model, the process of transforming C++ source code
into an executable program. We wrote, compiled, and ran our first program, a simple
main function that successfully returns an exit/return code.
We described the built-in arithmetic types that the language offers.
We learned how to declare and define variable names, and what the difference is
between references and pointers. We also saw the use of the const qualifier and
its advantages.
Furthermore, we talked about control flow statements and how to exploit them to
perform more complex actions.
Finally, we presented arrays and multidimensional arrays, and the operation to perform
to initialize them and access their values. In the next chapter, we will learn what
functions in C++ are, and how and why we should use them in our code.
Functions
2
Lesson Objectives

By the end of this chapter, you will be able to:

• Explain what functions are and how to declare them

• Utilize local and global variables

• Pass arguments to functions and return values from functions

• Create overloaded functions and call them appropriately

• Apply the concept of namespaces in organizing functions

In this chapter, we are going to look at functions in C++, how to use them, and why we would
want to use them.
44 | Functions

Introduction
Functions are a core tool in a programmer's toolkit for writing maintainable code. The
concept of a function is common in almost every programming language. Functions
have different names in various languages: procedures, routines, and many more, but
they all have two main characteristics in common:
• They represent a sequence of instructions grouped together.
• The sequence of instructions is identified by a name, which can be used to refer to
the function.

The programmer can call, or invoke a function when the functionalities provided by the
function are needed.
When the function is called, the sequence of instructions is executed. The caller can
also provide some data to the function to be used in operations within the program. The
following are the main advantages of using functions:
• Reduces repetition: It often occurs that a program needs to repeat the same
operations in different parts of the codebase. Functions allow us to write a single
implementation that is carefully tested, documented, and of high quality. This
code can be called from different places in the codebase, which enables code
reusability. This, in turn, increases the productivity of the programmer and the
quality of the software.
• Boosts code readability and modification: Often, we need several operations to
implement a functionality in our program. In these cases, grouping the operations
together in a function and giving a descriptive name to the function helps to
express what we want to do instead of how we do it.
Using functions greatly increases the readability of our code because it's now
composed of descriptive names of what we are trying to achieve, without the
noise of how the result is achieved.
In fact, it is easier to test and debug as you may only need to modify a function
without having to revisit the structure of the program.
Function Declaration and Definition | 45

• Higher level of abstraction: We can give a meaningful name to the function to


represent what it should achieve. This way, the calling code can be concerned
with what the function is supposed to do, and it does not need to know how the
operations are performed.

Note
Abstraction is the process of extracting all relevant properties from a class and
exposing them, while hiding details that are not important for a specific usage.

Let's take a tree as an example. If we were to use it in the context of an orchard, we


could abstract the tree to be a "machine" that takes a determined amount of space
and, given sunlight, water, and fertilizers, produces a certain number of fruits per
year. The property we are interested in is the tree's fruit production ability, so we
want to expose it and hide all the other details that are not relevant to our case.

In computer science, we want to apply the same concept: capture the key
fundamental properties of a class without showing the algorithm that implements
it.

A prime example of this is the sort function, which is present in many languages. We
know what the function expects and what it is going to do, but rarely are we aware
of the algorithm that is used to do it, and it might also change between different
implementations of the language.
In the following sections, we will demystify how function declaration and definition
works.

Function Declaration and Definition


A function declaration has the role of telling the compiler the name, the parameters,
and the return type of a function. After a function has been declared, it can be used in
the rest of the program.
The definition of the function specifies what operations a function performs.
46 | Functions

A declaration is composed of the type of the returned value, followed by the name of
the function and by a list of parameters inside a pair of parentheses. These last two
components form the signature of the function. The syntax of a function declaration is
as follows:
// Declaration: function without body
return_type function_name( parameter list );
If a function returns nothing, then the type void can be used, and if a function is not
expecting any parameters the list can be empty.
Let's look at an example of a function declaration:
void doNothingForNow();

Here, we declared a function named doNothingForNow(), which takes no arguments and


returns nothing. After this declaration, we can call the doNothingForNow() function in
our program.
To call a function that does not have any parameters, write the name of the function
followed by a pair of parentheses.
When a function is called, the execution flow goes from the body of the function
currently being executed to the body of the called function.
In the following example, the execution flow starts at the beginning of the body of main
function and starts executing its operations in order. The first operation it encounters
is the call to doNothingForNow(). At that point, the execution flow goes into the body of
doNothingForNow().
When all the operations inside a function are executed, or the function instructs them
to go back to the caller, the execution flow resumes from the operation after the
function call.
In our example, the operation after the function call prints Done on the console:
#include <iostream>

void doNothingForNow();

int main() {
doNothingForNow ();
std::cout << "Done";
}
Function Declaration and Definition | 47

If we were to compile this program, the compilation would succeed, but linking would
fail.
In this program, we instructed the compiler that a function called doNothingForNow()
exists and then we invoked it. The compiler generates an output that calls
doNothingForNow().
The linker then tries to create an executable from the compiler output, but since we did
not define doNothingForNow(), it cannot find the function's definition, so it fails.
To successfully compile the program, we need to define doNothingForNow(). In the next
section, we will explore how to define a function using the same example.

Defining a Function
To define a function, we need to write the same information that we used for the
declaration: the return type, the name of the function, and the parameter list, followed
by the function body. The function body delimits a new scope and is composed of a
sequence of statements delimited by curly braces.
When the function is executed, the statements are executed in order:
// Definition: function with body
return_type function_name( parameter_list ) {
statement1;
statement2;

...
last statement;
}
Let's fix the program by adding the body for doNothingForNow():
void doNothingForNow() {
// Do nothing
}
48 | Functions

Here, we defined doNothingForNow() with an empty body. This means that as soon as the
function execution starts, the control flow returns to the function that called it.

Note
When we define a function, we need to make sure that the signature (the return
value, the name, and the parameters) are the same as the declaration.

The definition counts as a declaration as well. We can skip the declaration if we


define the function before calling it.

Let's revisit our program now since we have added the definition for our function:
#include <iostream>

void doNothingForNow() {
// do nothing
}

int main() {
doNothingForNow();
std::cout << "Done";
}

If we compile and run the program, it will succeed and show Done on the output
console.
Function Declaration and Definition | 49

In a program, there can be multiple declarations of the same function, as long as the
declarations are the same. On the other hand, only a single definition of the function
can exist, as mandated by the One Definition Rule (ODR).

Note
Several definitions of the same function may exist if compiled in different files, but
they need to be identical. If they are not, then the program might do unpredictable
things.

The compiler is not going to warn you!

The solution is to have the declaration in a header file, and the definition in an
implementation file.
A header file is included in many different implementation files, and the code in these
files can call the function.
An implementation file is compiled only once, so we can guarantee that the definition is
seen only once by the compiler.
Then, the linker puts all of the outputs of the compiler together, finds a definition of the
function, and produces a valid executable.

Exercise 3: Calling a Function from main()


In our application, we want to log errors. To do so, we have to specify a function called
log(), which prints Error! to the standard output when called.
Let's create a function that can be called from several files, and put it in a different
header file that can be included:
1. Create a file named log.h and declare a function called log() with no parameters
and that returns nothing:
void log();
50 | Functions

2. Now, let's create a new file, log.cpp, where we define the log() function to print to
the standard output:
#include <iostream>
// This is where std::cout and std::endl are defined

void log() {
std::cout << "Error!" << std::endl;
}
3. Change the main.cpp file to include log.h and call log() in the main() function:
#include <log.h>

int main() {
log();
}
4. Compile the two files and run the executable. You will see that the message Error!
is printed when we execute it.

Local and Global Variables


The body of a function is a code block that can contain valid statements, one of which is
a variable definition. As we learned in Lesson 1, Getting Started, when such a statement
appears, the function declares a local variable.
This is in contrast to global variables, which are the variables that are declared outside
of functions (and classes, which we will look at in Lesson 3, Classes).
The difference between a local and a global variable is in the scope in which it is
declared, and thus, in who can access it.

Note
Local variables are in the function scope and can only be accessed by the function.
On the contrary, global variables can be accessed by any function that can see
them.
Local and Global Variables | 51

It is desirable to use local variables over global variables because they enable
encapsulation: only the code inside the function body can access and modify the
variable, making the variable invisible to the rest of the program. This makes it easy
to understand how a variable is used by a function since its usage is restricted to the
function body and we are guaranteed that no other code is accessing it.
Encapsulation is usually used for three separate reasons, which we will explore in more
detail in Lesson 3, Classes:
• To restrict the access to data used by a functionality
• To bundle together the data and the functionality that operates on it
• Encapsulation is a key concept that allows you to create abstractions

On the other hand, global variables can be accessed by any function.


This makes it hard to be sure of the function's value when interacting with them, unless
we know not only what our function does, but also what all the other code in the
program that interacts with the global variable does.
Additionally, code that we add later to the program, might start modifying the global
variable in a way that we did not expect in our function, breaking the functionality of
our function without ever modifying the function itself. This makes it extremely difficult
to modify, maintain, and evolve programs.
The solution to this problem is to use the const qualifier so that no code can change the
variable, and we can treat it as a value that never changes.

Note
Always use the const qualifier with global variables whenever possible.

Try to avoid using mutable global variables.

It is a good practice to use global const variables instead of using values directly in
the code. They allow you to give a name and a meaning to the value, without any
of the risks that come with mutable global variables.
52 | Functions

Working with Variable Objects


It is important to understand the relationship between variables, objects, and the
lifetime of objects in C++ to write programs correctly.

Note
An object is a piece of data in the program's memory.

A variable is a name we give to an object.

There is a distinction in C++ between the scope of a variable and the lifetime of the
object it refers to. The scope of a variable is the part of the program where the variable
can be used.
The lifetime of an object, on the contrary, is the time during execution wherein the
object is valid to access.
Let's examine the following program to understand the lifetime of an object:
#include <iostream>
/* 1 */ const int globalVar = 10;
int* foo(const int* other) {
/* 5 */ int fooLocal = 0;
std::cout << "foo's local: " << fooLocal << std::endl;
std::cout << "main's local: " << *other << std::endl;
/* 6 */ return &fooLocal;
}
int main()
{
/* 2 */ int mainLocal = 15;
/* 3 */ int* fooPointer = foo(&mainLocal);
std::cout << "main's local: " << mainLocal << std::endl;
Local and Global Variables | 53

std::cout << "We should not access the content of fooPointer! It's not
valid." << std::endl;
/* 4 */ return 0;
}

Figure 2.1: Lifetime of an object

The lifetime of a variable starts when it is initialized and ends when the containing
block ends. Even if we have a pointer or reference to a variable, we should access it only
if it's still valid. fooPointer is pointing to a variable which is no longer valid, so it should
not be used!
When we declare a local variable in the scope of a function, the compiler automatically
creates an object when the function execution reaches the variable declaration; the
variable refers to that object.
When we declare a global variable instead, we are declaring it in a scope that does not
have a clear duration – it is valid for the complete duration of the program. Because of
this, the compiler creates the object when the program starts before any function is
executed – even the main() function.
The compiler also takes care of terminating the object's lifetime when the execution
exits from the scope in which the variable has been declared, or when the program
terminates in the case of a global variable. The termination of the lifetime of an object is
usually called destruction.
Variables declared in a scope block, either local or global, are called automatic
variables, because the compiler takes care of initializing and terminating the lifetime of
the object associated with the variables.
54 | Functions

Let's look at an example of a local variable:


void foo() {
int a;
}
In this case, the variable a is a local variable of type int. The compiler automatically
initializes the object it refers to with what is called its default initialization when the
execution reaches that statement, and the object will be destroyed at the end of the
function, again, automatically.

Note
The default initialization of basic types, such as integers, is doing nothing for us.
This means that the variable a will have an unspecified value.

If multiple local variables are defined, the initialization of the objects happens in the
order of declaration:
void foo() {
int a;
int b;
}

Variable a is initialized before b. Since variable b was initialized after a, its object is
destroyed before the one a refers to.
If the execution never reaches the declaration, the variable is not initialized. If the
variable is not initialized, it is also not destroyed:
void foo() {
if (false) {
int a;
}
int b;
}
Local and Global Variables | 55

Here, the variable a is never default initialized, and thus never destroyed. This is similar
for global variables:
const int a = 1;

void main() {
std::cout << "a=" << a << std::endl;
}
Variable a is initialized before the main() function is called and is destroyed after we
return the value from the main() function.

Exercise 4: Using Local and Global Variables in a Fibonacci Sequence


We want to write a function that returns the 10th number in a Fibonacci sequence.

Note
The nth Fibonacci number is defined as the sum of the n-1th and the n-2th, with the
first number in the sequence being 0 and the second being 1.

Example:

10th Fibonacci number = 8th Fibonacci number + 9th Fibonacci number

We want to use the best practice of giving a name and a meaning to values, so instead of
using 10 in the code, we are going to define a const global variable, named POSITION.
We will also use two local variables in the function to remember the n-1th and the n-2th
number:
1. Write the program and include the following constant global variable after the
header file:
#include <iostream>

const int POSITION = 10;


const int ALREADY_COMPUTED = 3;
56 | Functions

2. Now, create a function named print_tenth_fibonacci() with the return type as


void:
void print_tenth_fibonacci()
3. Within the function, include three local variables, named n_1, n_2, and current of
type int, as shown here:
int n_1 = 1;
int n_2 = 0;
int current = n_1 + n_2;
4. Let's create a for loop to generate the remaining Fibonacci numbers until we
reach the 10th, using the global variables we defined previously as starting and
ending indices:
for(int i = ALREADY_COMPUTED; i < POSITION; ++i){
n_2 = n_1;
n_1 = current;
current = n_1 + n_2;
}
5. Now, after the previous for loop, add the following print statement to print the
last value stored in the current variable:
std::cout << current << std::endl;
6. In the main() function, call print_tenth_fibonacci() and print the value of the 10th
element of the Fibonacci sequence:
int main() {
std::cout << "Computing the 10th Fibonacci number" << std::endl;
print_tenth_fibonacci();
}
Let's understand the variable data flow of this exercise. First, the n_1 variable is
initialized, then n_2 is initialized, and right after that, current is initialized. And then,
current is destroyed, n_2 is destroyed, and finally, n_1 is destroyed.
i is also an automatic variable in the scope that's created by the for loop, so it is
destroyed at the end of the for loop scope.
Passing Arguments and Returning Values | 57

For each combination of cond1 and cond2, identify when initialization and destruction
occurs in the following program:
void foo()
if(cond1) {
int a;
}
if (cond2) {
int b;
}
}

Passing Arguments and Returning Values


In the Introduction section, we mentioned that the caller can provide some data to the
function. This is done by passing arguments to the parameters of the function.
The parameters that a function accept are part of its signature, so we need to specify
them in every declaration.
The list of parameters a function can accept is contained in the parentheses after the
function name. The parameters in the function parentheses are comma-separated,
composed by a type, and optionally an identifier.
For example, a function taking two integer numbers would be declared as follows:
void two_ints(int, int);

If we wanted to give a name to these parameters, a and b respectively, we would write


the following:
void two_ints(int a, int b);

Inside its body, the function can access the identifiers defined in the function signature
as if they were declared variables. The values of the function parameters are decided
when the function is called.
To call a function that takes a parameter, you need to write the name of the function,
followed by a list of expressions inside a pair of parentheses:
two_ints(1,2);
58 | Functions

Here, we called the two_ints function with two arguments: 1 and 2.


The arguments used to call the function initialize the parameters that the function is
expecting. Inside the two_ints function, variable a will be equal to 1, and b will be equal
to 2.
Each time the function is called, a new set of parameters is initialized from the
arguments that were used to call the function.

Note
Parameter: This is a variable that was defined by a function, and can be used to
provide data as per the code.

Argument: The value the caller wants to bind to the parameters of the function.

In the following example, we used two values, but we can also use arbitrary expressions
as arguments:
two_ints(1+2, 2+3);

Note
The order in which the expression is evaluated is not specified!

This means that when calling two_ints(1+2, 2+3);, the compiler might first execute
1+2 and then 2+3, or 2+3 and then 1+2. This is usually not a problem if the expression
does not change any state in the program, but it can create bugs that are hard to detect
when it does. For example, given int i = 0;, if we call two_ints(i++, i++), we don't
know whether the function is going to be called with two_ints(0, 1) or two_ints(1, 0).
In general, it's better to declare expressions that change the state of the program
in their own statements, and call functions with expressions that do not modify the
program's state.
The function parameters can be of any type. As we already saw, a type in C++ could be
a value, a reference, or a pointer. This gives the programmer a few options on how to
accept parameters from the callers, based on the behavior it wants.
In the following subsections, we will explore the working mechanism of Pass by value
and Pass by reference in more detail.
Passing Arguments and Returning Values | 59

Pass by Value
When the parameter type of a function is a value type, we say that the function is taking
an argument by value or the argument is passed by value.
When a parameter is a value type, a new local object is created each time the function is
called.
As we saw with automatic variables, the lifetime of the object lasts until the execution
does not reach the end of the function's scope.
When the parameter is initialized, a new copy is made from the argument provided
when invoking the function.

Note
If you want to modify a parameter but do not want or do not care about the calling
code seeing the modification, use pass by value.

Exercise 5: Calculating Age using Pass by Value Arguments


James wants to write a C++ program to calculate what the age of a person will be after
five years by providing their current age as an input.
To implement such a program, he is going to write a function that takes a person's age
by value and computes how old they will be in 5 years, and then prints it on the screen:
1. Create a function named byvalue_age_in_5_years, as illustrated here. Make sure
that the value in the calling code does not change:
void byvalue_age_in_5_years(int age) {
age += 5;
std::cout << "Age in 5 years: " << age << std::endl;
// Prints 100
}
60 | Functions

2. Now, in main(), call the function we created in the previous step by passing the
variable age as a value:
int main() {
int age = 95;
byvalue_age_in_5_years(age);
std::cout << "Current age: " << age;
// Prints 95
}

Note
Pass by value should be the default way of accepting arguments: always use it
unless you have a specific reason not to.

The reason for this is that it makes the separation between the calling code and
the called function stricter: the calling code cannot see the changes that the called
function makes on the parameters.

Passing parameters by value creates a clear boundary between the calling function and
the called function, because the parameters are copied:
1. As the calling function, we know that the variables we passed to the functions will
not be modified by it.
2. As the called function, we know that even if we modify the provided parameters,
there will be no impact on the called function.

This makes it easy to understand the code, because the changes we make to the
parameters have no impact outside of the function.
Pass by value can be the faster option when taking an argument, especially if the
memory size of the argument is small (for example, integers, characters, float, or small
structures).
Passing Arguments and Returning Values | 61

We need to remember though that passing by value performs a copy of the argument.
Sometimes, this can be an expensive operation both in terms of memory and processing
time, like when copying a container with many elements.
There are some cases where this limitation can be overcome with the move semantic
that was added in C++11. We will see more of it in Lesson 3, Classes.
Let's look at an alternative to pass by value that has a different set of properties.

Pass by Reference
When the parameter type of the function is a reference type, we say that the function is
taking an argument by reference or the argument is passed by reference.
We saw earlier that a reference type does not create a new object – it is simply a new
variable, or name that refers to an object that already exists.
When the function that accepts the argument by reference is called, the reference is
bound to the object used in the argument: the parameter will refer to the given object.
This means that the function has access to the object the calling code provided and can
modify it.
This is convenient if the goal of the function is to modify an object, but it can be more
difficult to understand the interaction between the caller and the called function in
such situations.

Note
Unless the function must modify the variable, always use const references, as we
will see later.
62 | Functions

Exercise 6: Calculating Incrementation of Age using Pass by Reference


James would like to write a C++ program which, given anyone's age as input, prints
Congratulations! if their age will be 18 or older in the next 5 years.
Let's write a function that accepts its parameters by reference:
1. Create a function named byreference_age_in_5_years() of type void, as illustrated
here:
void byreference_age_in_5_years(int& age) {
age += 5;
}
2. Now, in main(), call the function we created in the previous step by passing the
variable age as a reference:
int main() {
int age = 13;
byreference_age_in_5_years(age);
if (age >= 18) {
std::cout << "Congratulations! " << std::endl;
}
}
Contrary to passing by value the speed when passing by reference does not change
when the memory size of the object passed.
This makes pass by reference the preferred method when copying an object, since
providing pass by value to the function is expensive, especially if we cannot use the move
semantic that was added in C++11.

Note
If you want to use pass by reference, but you are not modifying the provided
object, make sure to use const.

With C++, we can use std::cin to read input from the console executing the program.
When writing std::cin >> variable;, the program will block waiting for some user
input, and then it will populate variable with the value read from the input as long as it
is a valid value and the program knows how to read it. By default, we can assign all the
built-in data types and some types defined in the standard library, such as string.
Working with const References or r-value References | 63

Activity 3: Checking Voting Eligibility


James is creating a program to print a message on the console screen: "Congratulations!
You are eligible to vote in your country" or "No worries, just <value> more years to go."
after the user provides their current age as input.
1. Create a function named byreference_age_in_5_years(int& age) and add the
following code:
#include <iostream>

void byreference_age_in_5_years(int& age) {


if (age >= 18) {
std::cout << "Congratulations! You are eligible to vote for your
nation." << std::endl;
return;
2. In the else block, add the code to calculate the years remaining until they can
vote:
} else{
int reqAge = 18;
}
}
3. In main(), add the input stream, as illustrated, to accept the input from the user.
Pass the value as a reference in the previous function:
int main() {
int age;
std::cout << "Please enter your age:";
std::cin >> age;
The solution for this activity can be found on page 284.

Working with const References or r-value References


A temporary object cannot be passed as an argument for a reference parameter. To
accept temporary parameters, we need to use const references or r-value references.
The r-value references are references that are identified by two ampersands, &&, and
can only refer to temporary values. We will look at them in more detail in Lesson 4,
Generic Programming and Templates.
64 | Functions

We need to remember that a pointer is a value that represents the location of an object.
Being a value, it means that when we are accepting a parameter as a pointer, the pointer
itself is passed as a value.
This means that the modification of the pointer inside the function is not going to be
visible to the caller.
But if we are modifying the object the pointer points to, then the original object is going
to be modified:
void modify_pointer(int* pointer) {
*pointer = 1;
pointer = 0;
}
int main() {
int a = 0;
int* ptr = &a;
modify_pointer(ptr);
std::cout << "Value: " << *ptr << std::endl;
std::cout << "Did the pointer change? " << std::boolalpha << (ptr == &a);
}
Most of the time, we can think of passing a pointer as passing a reference, with the
caveat that you need to be aware that the pointer might be null.
Accepting a parameter as a pointer is mainly used for three reasons:
• Traversing the elements of an array, by providing the start pointer and either the
end pointer or the size of the array.
• Optionally modifying a value. This means that the function modifies a value if it is
provided.
• Returning more than a single value. This is often done to set the value of a pointer
passed as an argument and then return an error code to signal whether the
operation was performed.

We will see in Lesson 4, Generic Programming and Templates, how features introduced
in C++11 and C++17 allow us to avoid using pointers for some of these use cases,
eliminating the possibility of some common classes of errors, such as dereferencing
invalid pointers or accessing unallocated memory.
Working with const References or r-value References | 65

The options of passing by value or passing by reference are applicable to every single
parameter the function expects, independently.
This means that a function can take some arguments by value and some by reference.

Returning Values from Functions


Up until now, we have seen how to provide values to a function. In this section, we will
see how a function can provide value back to the caller.
We said earlier that the first part of a function declaration is the type returned by the
function: this is often referred to as the function's return type.
All the previous examples used void to signal that they were returning nothing. Now, it
is time to look at an example of a function returning a value:
int sum(int, int);

The previous function accepts two integers by value as parameters and returns an
integer.
The invocation of the function in the caller code is an expression evaluating to an
integer. This means that we can use it anywhere that an expression is allowed:
int a = sum(1, 2);

A function can return a value by using the return keyword, followed by the value it
wants to return.
The function can use the return keyword several times inside its body, and each time
the execution reaches the return keyword, the program will stop executing the function
and go back to the caller, with the value returned by the function, if any. Let's look at
the following code:
void rideRollercoasterWithChecks(int heightInCm) {
if (heightInCm < 100) {
std::cout << "Too short";
return;
}
if (heightInCm > 210) {
std::cout << "Too tall";
return;
}
rideRollercoaster();
66 | Functions

// implicit return at the end of the function


}
A function also returns to the caller if it reaches the end of its body.
This is what we did in the earlier examples since we did not use the return keyword.
Not explicitly returning can be okay if a function has a void return type. However, it will
give unexpected results if the function is expected to return a value: the returned type
will have an unspecified value and the program will not be correct.
Be sure to enable the warning, as it will save you a lot of debugging time.

Note
It is surprising, but every major compiler allows the compiling of functions, which
declare a return type other than void, but don't return a value.

This is easy to spot in simple functions, but it is much harder in complex ones with
lots of branches.

Every compiler supports options to warn you if a function returns without


providing a value.

Let's look at an example of a function returning an integer:


int sum(int a, int b) {
return a + b;
}
As we said earlier, a function can use the return statement several times inside its body,
as shown in the following example:
int max(int a, int b) {
if(a > b) {
return a;
} else {
return b;
}
}
Working with const References or r-value References | 67

We always return a value that's independent of the values of the arguments.

Note
It is a good practice to return as early as possible in an algorithm.

The reason for this is that as you follow the logic of the code, especially when
there are many conditionals, a return statement tells you when that execution
path is finished, allowing you to ignore what happens in the remaining part of the
function.

If you only return at the end of the function, you always have to look at the full
code of the function.

Since a function can be declared to return any type, we have to decide whether to
return a value or a reference.

Returning by Value
A function whose return type is a value type is said to return by value.
When a function that returns by value reaches a return statement, the program
creates a new object, which is initialized from the value of the expression in the return
statement.
In the previous function, sum, when the code reaches the stage of returning a + b, a new
integer is created, with the value equal to the sum of a and b, and is returned.
On the side of the caller, int a = sum(1,2);, a new temporary automatic object is
created and is initialized from the value returned by the function (the integer that was
created from the sum of a and b).
This object is called temporary because its lifetime is valid only while the full-
expression in which it is created is executed. We will see in the Returning by Reference
section, what this means and why this is important.
The calling code can then use the returned temporary value in another expression or
assign it to a value.
Add the end of the full expression, since the lifetime of the temporary object is over, it
is destroyed.
68 | Functions

In this explanation, we mentioned that objects are initialized several times while
returning a value. This is not a performance concern as C++ allows compilers to
optimize all these initializations, and often initialization happens only once.

Note
It is preferable to return by value as it's often easier to understand, easier to use,
and as fast as returning by reference.

How can returning by value be so fast? C++11 introduced the move semantic, which
allows moving instead of copying the return types when they support the move
operation. We'll see how in Lesson 3, Classes. Even before C++11, all mainstream
compilers implemented return value optimization (RVO) and named return
value optimization (NRVO), where the return value of a function is constructed
directly in the variable into which they would have been copied to when returned.
In C++17, this optimization, also called copy elision, became mandatory.

Returning by Reference
A function whose return type is a reference is said to return by reference.
When a function returning a reference reaches a return statement, a new reference is
initialized from the expression that's used in the return statement.
In the caller, the function call expression is substituted by the returned reference.
However, in this situation, we need to also be aware of the lifetime of the object the
reference is referring to. Let's look at an example:
const int& max(const int& a, const int& b) {
if (a > b) {
return a;
} else {
return b;
}
}
Working with const References or r-value References | 69

First, we need to note that this function already has a caveat. The max function is
returning by value, and it did not make a difference if we returned a or b when they
were equal.
In this function, instead, when a == b we are returning b, this means that the code
calling this function needs to be aware of this distinction. In the case where a function
returns a non-const reference it might modify the object referred to by the returned
reference, and whether a or b is returned might make a difference.
We are already seeing how references can make our code harder to understand.
Let's look at the function we used:
int main() {
const int& a = max(1,2);
std::cout << a;
}

This program has an error! The reason is that 1 and 2 are temporary values, and as
we explained before, a temporary value is alive until the end of the full expression
containing it.
To better understand what is meant by "the end of the full expression containing it", let's
look at the code we have in the preceding code block: int& a = max(1,2);. There are
four expressions in this piece of code:
• 1 is an integer literal, which still counts as an expression
• 2 is an integer literal, similar to 1
• max(expression1, expression2) is a function call expression
• a = expression3 is an assignment expression

All of this happens in the declaration statement of variable a.


The third point covers the function call expression, while containing the full expression
is covered in the following point.
This means that lifetimes 1 and 2 will stop at the end of the assignment. But we got a
reference to one of them! And we are using it!
Accessing an object whose lifetime is terminated is prohibited by C++, and this will
result in an invalid program.
70 | Functions

In a more complex example, such as int a = max(1,2) + max(3,4);, the temporary


objects returned by the max functions will be valid until the end of the assignment, but
no longer.
Here, we are using the two references to sum them, and then we assign the result as
a value. If we assigned the result to a reference, as in the following example, int& a =
max(1,2) + max(3,4);, instead, the program would have been wrong.
This sounds confusing, but it is important to understand as it can be a source of hard-
to-debug problems if we use a temporary object after the full expression in which it's
created has finished executing.
Let's look at another common mistake in functions returning references:
int& sum(int a, int b) {
int c = a + b;
return c;
}

We created a local, automatic object in the function body and then we returned a
reference to it.
In the previous section, we saw that local objects' lifetimes end at the end of the
function. This means that we are returning a reference to an object whose lifetime will
always be terminated.
Earlier, we mentioned the similarities between passing arguments by reference and
passing arguments by pointers.
This similarity persists when returning pointers: the object pointed to by a pointer
needs to be alive when the pointer is later dereferenced.
So far, we have covered examples of mistakes while returning by reference. How can
references be used correctly as return types to functions?
The important part of using references correctly as return values is to make sure that
the object outlives the reference: the object must always be alive – at least until there is
a reference to it.
Working with const References or r-value References | 71

A common example is accessing a part of an object, for example, using an std::array,


which is a safe option compared to the built-in array:
int& getMaxIndex(std::array<int, 3>& array, int index1, int index2) {
/* This function requires that index1 and index2 must be smaller than 3!
*/
int maxIndex = max(index1, index2);
return array[maxIndex];

The calling code would look as follows:


int main() {
std:array<int, 3> array = {1,2,3};
int& elem = getMaxIndex(array, 0, 2);
elem = 0;
std::cout << array[2];
// Prints 0
}

In this example, we are returning a reference to an element inside an array, and the
array remains alive longer than the reference.
The following are guidelines for using return by reference correctly:
• Never return a reference to a local variable (or a part of it)
• Never return a reference to a parameter accepted by value (or a part of it)

When returning a reference that was received as a parameter, the argument passed to
the function must live longer than the returned reference.
Apply the previous rule, even when you are returning a reference to a part of the object
(for example, an element of an array).
72 | Functions

Activity 4: Using Pass by Reference and Pass by Value


In this activity, we are going to see the different trade-offs that can be made when
writing a function, depending on the parameters the function accepts:
1. Write a function that takes two numbers and returns the sum. Should it take the
arguments by value or reference? Should it return by value or by reference?
2. After that, write a function that takes two std::arrays of ten integers and an index
(guaranteed to be less than 10) and returns the greater of the two elements to the
given index in the two arrays.
3. The calling function should then modify the element. Should it take the arguments
by value or reference? Should it return by value or by reference? What happens if
the values are the same?

Take the arrays by reference and return by reference because we are saying that the
calling function is supposed to modify the element. Take the index by value since there
is no reason to use references.
If the values are the same, the element from the first array is returned.

Note
The solution to this activity can be found at page 285.

Const Parameters and Default Arguments


In the previous chapter, we saw how and when to use references in function parameters
and return types. C++ has an additional qualifier, the const qualifier, which can be used
independently from the ref-ness (whether the type is a reference or not) of the type.
Let's see how const is used in the various scenarios we investigated when looking at
how functions can accept parameters.

Passing by const Value


In pass by value, the function parameter is a value type: when invoked, the argument is
copied into the parameter.
This means that regardless of whether const is used in the parameter or not, the calling
code cannot see the difference.
Const Parameters and Default Arguments | 73

The only reason to use const in the function signature is to document to the
implementation that it cannot modify such a value.
This is not commonly done, as the biggest value of a function signature is for the caller
to understand the contract of calling the function. Because of this, it is rare to see int
max(const int, const int), even if the function does not modify the parameters.
There is an exception, though: when the function accepts a pointer.
In such cases, the function wants to make sure that it is not assigning a new value to the
pointer. The pointer acts similar to a reference here, since it cannot be bound to a new
object, but provides nullability.
An example could be setValue(int * const), a function that takes a const pointer to an
int.
The integer is not const, so it can be changed, but the pointer is const and the
implementation cannot change it during implementation.

Passing by const Reference


Const is extremely important in pass by reference, and any time you use a reference
in the parameters of a function, you should also add const to it (if the function is not
designed to modify it).
The reason for this is that a reference allows you to modify the provided object freely.
It is error-prone, since the function might modify an object the caller does not expect
to be modified by mistake, and it is hard to understand as there is no clear boundary
between the caller and the function, again, because the function can modify the state of
the caller.
const instead fixes the problem, since a function cannot modify an object through a
const reference.
This allows the function to use reference parameters without some of the drawbacks of
using references.
The function should remove the const from a reference, but only if it is intended to
modify the provided object, otherwise every reference should be const.
Another advantage of const reference parameters is that temporary objects can be used
as arguments for them.
74 | Functions

Returning by const Value


There is no widespread reason to return by const value since the calling code often
assigns the value to a variable, in which case the const-ness of the variables is going
to be the deciding factor, or passes the value to a next expression, and it is rare for an
expression to expect a const value.
Returning by const value also inhibits the move semantic of C++11, thus reducing
performance.

Returning by const Reference


A function should always return by const reference when the returned reference is
meant to only be read and not be modified by the calling code.
The same concept we applied to object lifetimes when returning references to them
also apply to const:
• When returning a reference accepted as a parameter, if the parameter is a const
reference, the returned reference must be const as well
• When returning a reference to a part of an object accepted as the const reference
parameter, the returned reference must be const as well

A parameter accepted as a reference should be returned as a const reference if the


caller is not expected to modify it.
Sometimes, the compilation fails, stating that the code is trying to modify an object
that is a const reference. Unless the function is meant to modify the object, the solution
is not to remove const from the reference in the parameter. Instead, look for why
the operation that you are trying to perform does not work with const, and what the
possible alternatives are.
const is not about the implementation, it is about the meaning of the function.
When you write the function signature, you should decide whether to use const, as the
implementation will have to find a way to respect that.
For example:
void setTheThirdItem(std::array<int, 10>& array, int item)

This should clearly take a reference to the array since its purpose is to modify the array.
On the other hand, we can use the following:
int findFirstGreaterThan(const std::array<int, 10>& array, int threshold)
Default Arguments | 75

This tells us that we are only looking into the array – we are not changing it, so we
should use const.

Note
It is a best practice to use const as much as possible, as it allows the compiler to
make sure that we are not modifying objects that we do not want to modify.

This can help to prevent bugs.

It also helps to keep another best practice in mind: never use the same variable
to represent different concepts. Since the variable cannot be changed, it is less
natural to reuse it instead of creating a new one.

Default Arguments
Another feature C++ provides to make life easier for the caller when it comes to calling
functions are default arguments.
Default arguments are added to a function declaration. The syntax is to add an = sign
and supply the value of the default argument after the identifier of the parameter of the
function. An example of this would be:
int multiply(int multiplied, int multiplier = 1);

The caller of the function can call multiply either with 1 or 2 arguments:
multiply(10); // Returns 10
multiply(10, 2); // Returns 20
When an argument with a default value is omitted, the function uses the default value
instead. This is extremely convenient if there are functions with sensible defaults that
callers mostly do not want to modify, except in specific cases.
Imagine a function that returns the first word of a string:
char const * firstWord(char const * string, char separator = ' ').
Most of the time, a word is separated by a whitespace character, but a function can
decide whether or not it should use a different separator. The fact that a function offers
the possibility to provide a separator is not forcing most callers, which simply want to
use the space, to specify it.
76 | Functions

It is a best practice to set the default arguments in the function signature declaration,
and not declare them in the definition.

Namespaces
One of the goals of functions is to better organize our code. To do so, it is important to
give meaningful names to them.
For example, in package management software, there might be a function called sort
for sorting packages. As you can see, the name is the same as the function that would
sort a list of numbers.
C++ has a feature that allows you to avoid these kinds of problems and groups names
together: namespaces.
A namespace starts a scope in which all the names declared inside are part of the
namespace.
To create a namespace, we use the namespace keyword, followed by the identifier and
then the code block:
namespace example_namespace {
// code goes here
}
To access an identifier inside a namespace, we prepend the name of the namespace to
the name of the function.
Namespaces can be nested as well. Simply use the same declaration as before inside the
namespace:
namespace parent {
namespace child {
// code goes here
}
}
To access an identifier inside a namespace, you prepend the name of the identifier with
the name of the namespace in which it is declared, followed by ::.
You might have noticed that, before we were using std::cout. This is because the C++
standard library defines the std namespace and we were accessing the variable named
cout.
Namespaces | 77

To access an identifier inside multiple namespaces, you can prepend the list of all the
namespaces separated by :: – parent::child::some_identifier. We can access names in
the global scope by prepending :: to the name—::name_in_global_scope.
If we were to only use cout, the compiler would have told us that the name does not
exist in the current scope.
This is because the compiler searches only in the current namespace and the parent
namespaces to find an identifier by default, so unless we specify the std namespace, the
compiler will not search in it.
C++ helps make this more ergonomic with the help of the using declaration.
The using declaration is defined by the using keyword, followed by an identifier
specified with its namespaces.
For example, using std::cout; is a using declaration that declares that we want to use
cout. When we want to use all the declarations from a namespace, we can write using
namespace namespace_name;. For example, if we want to use every name defined in the
std namespace, we would write: using namespace std;.
When a name is declared inside the using declaration, the compiler also looks for that
name when looking for an identifier.
This means that, in our code, we can use cout and the compiler will find std::cout.
A using declaration is valid as long as we are in the scope in which it is declared.

Note
To better organize your code and avoid naming conflicts, you should always put
your code inside a namespace that's specific to either your application or library.

Namespaces can also be used to specify that some code is used only by the
current code.

Let's imagine you have a file called a.cpp that contains int default_name = 0; and
another file called b.cpp with int default_name = 1;. When you compile the two files
and link them together, we get an invalid program: the same variable has been declared
with two different values, and this violates the One Definition Rule (ODR).
78 | Functions

But you never meant for those to be the same variable. To you, they were some
variables that you just wanted to use inside your .cpp file.
To tell that to the compiler, you can use anonymous namespaces: a namespace with no
identifier.
All the identifiers created inside it will be private to the current translation unit
(normally the .cpp file).
How can you access an identifier inside an anonymous namespace? You can access the
identifier directly, without the need to use the namespace name, which does not exist,
or the using declaration.

Note
You should only use anonymous namespaces in .cpp files.

Activity 5: Organizing Functions in Namespaces


Write a function to read the name of a car for a lottery in a namespace based on
numerical input. If the user inputs 1, they win a Lamborghini, and if the user inputs 2,
they win a Porsche:
1. Define the first namespace as LamborghiniCar with an output() function that will
print "Congratulations! You deserve the Lamborghini." when called.
2. Define the second namespace as PorscheCar with an output() function that will
print "Congratulations! You deserve the Porsche." when called.
3. Write a main function to read the input of numbers 1 and 2 into a variable called
magicNumber.
4. Create an if-else loop with the if condition calling the first namespace with
LamborghiniCar::output() if the input is 1. Otherwise, the second namespace is
called similarly when the input is 2.
5. If neither of these conditions are met, we print a message asking them to enter a
number between 1 and 2.

Note
The solution for this activity can be found on page 285.
Function Overloading | 79

Function Overloading
We saw how C++ allows us to write a function that takes parameters either by value or
by reference, using const, and organizes them in namespaces.
There is an additional powerful feature of C++ that allows us to give the same name
to functions that perform the same conceptual operation on different types: function
overloading.
Function overloading is the ability to declare several functions with the same name –
that is, if the set of parameters they accept is different.
An example of this is the multiply function. We can imagine this function being defined
for integers and floats, or even for vectors and matrices.
If the concept represented by the function is the same, we can provide several
functions that accept different kinds of parameters.
When a function is invoked, the compiler looks at all the functions with that name,
called the overload set, and picks the function that is the best match for the arguments
provided.
The precise rule on how the function is selected is complex, but the behavior is often
intuitive: the compiler looks for the better match between the arguments and the
expected parameters of the function. If we have two functions, int increment(int)
and float increment(float), and we call them with increment(1), the integer overload
is selected because an integer is a better match to an integer than a float, even if an
integer can be converted into a float. An example of this would be:
bool isSafeHeightForRollercoaster(int heightInCm) {
return heightInCm > 100 && heightInCm < 210;
}

bool isSafeHeightForRollercoaster(float heightInM) {


return heightInM > 1.0f && heightInM < 2.1f;
}

// Calls the int overload


isSafeHeightForRollercoaster(187);

// Class the float overload


isSafeHeightForRollercoaster(1.67f);
80 | Functions

Thanks to this feature, the calling code does not need to worry about which overload
of the function the compiler is going to select, and the code can be more expressive
thanks to using the same function to express the same meaning.

Activity 6: Writing a Math Library for a 3D Game


Johnny wants to implement a math library for the video game he is making. It will be a
3D game, so he will need to operate on points representing the three coordinates: x, y,
and z.
The points are represented as std::array<float, 3>. A library will be used throughout
the game, so Johnny needs to be sure it can work when included multiple times (by
creating a header file and declaring the functions there).
The library needs to support the following steps:
1. Finding the distance between 2 floats, 2 integers, or 2 points.
2. If only one of the 2 points is provided, the other one is assumed to be the origin
(the point at location (0,0,0)).
3. Additionally, Johnny often needs to compute the circumference of a circle from its
radius (defined as 2*pi*r) to understand how far enemies can see. pi is constant
for the duration of the program (which can be declared globally in the .cpp file).
4. When an enemy moves, it visits several points. Johnny needs to compute the total
distance that it would take to walk along those points.
5. For simplicity, we will limit the number of points to 10, but Johnny might need
up to 100. The function would take std::array<std::array<float, 3>, 10> and
compute the distance between consecutive points.
For example (with a list of 5 points): for the array {{0,0,0}, {1,0,0}, {1,1,0},
{0,1,0}, {0,1,1}}, the total distance is 5, because going from {0,0,0} to {1,0,0} is
a distance of 1, then going from {1,0,0} to {1,1,0} is a distance of 1 again, and so
on for the remaining 3 points.

Note
The solution for this activity can be found on page 286.
Summary | 81

Make sure that the functions are well-organized by grouping them together.
Remember that the distance between two points is computed as the square root of
(x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2.
C++ offers the std::pow function for the power function, which takes the base and the
exponent, and the std::sqrt function, which takes the number to square. Both are in
the cmath header.

Summary
In this chapter, we saw the powerful features C++ offers to implement functions.
We started by discussing why functions are useful and what they can be used for, and
then we dove into how to declare and define them.
We analyzed different ways of accepting parameters and returning values, how to make
use of local variables, and then explored how to improve the safety and convenience of
calling them with const and default arguments.
Finally, we saw how functions can be organized in namespaces and the ability to give
the same name to different functions that implement the same concept, making the
calling code not have to think about which version to call.
In the next chapter, we will look at how to create classes and how they are used in C++
to make building complex programs easy and safe.
3
Classes

Lesson Objectives

By the end of this chapter, you will be able to:

• Declare and define a class

• Access the members of a class using objects

• Apply access modifiers to encapsulate data

• Use the static modifier on data members and member functions

• Implement a nested class

• Utilize the friend specifier to access private and protected members

• Use constructors, copy constructors, assignment operators, and destructors

• Overload operators

• Implement functors

In this chapter, we will be learning about classes and objects in C++.


84 | Classes

Introduction
In the previous chapter, we saw how we can use functions to combine basic operations
into units with a clear meaning. Additionally, in the first chapter, we saw how, in C++, we
can store data in basic types, such as integers, chars, and floats.
In this chapter, we will be covering how to define and declare classes and how to access
member functions of a class. We will explore what member and friend functions are and
how to use each in a program. Later in the chapter, we will look at how constructors
and destructors work. At the end of the chapter, we will explore functors and how you
can use them in your programs.

Declaring and Defining a Class


A class is a way to combine data and operations together to create new types that can
be used to represent complex concepts.
Basic types can be composed to create more meaningful abstractions. For example,
location data is composed of latitude and longitude coordinates, which are represented
as float values. With such a representation, when our code needs to operate on
a location, we would have to provide both the latitude and longitude as separate
variables. This is error-prone, as we might forget to pass one of the two variables, or we
could provide them in the wrong order.
Additionally, computing the distance between two coordinates is a complex task and we
don't want to write the same code again and again. It becomes even more difficult when
we use more complex objects.
Continuing our example on Coordinates, instead of using operations on two float
types, we can define a type, which stores the location and provides the necessary
operations to interact with it.

The Advantages of Using Classes


Classes provide several benefits, such as abstraction, information hiding, and
encapsulation. Let's explore each of these in depth:
• Abstraction: This allows us to represent a high-level concept. In our previous
example with GPS coordinates, we can see that, without classes we need to use
two float variables, but this does not represent the concept that we want to
use. The programmer needs to remember that the two variables have a different
meaning and should be used together. Classes allow us to explicitly define a
concept, composed by data and operations on that data, and assign a name to it.
Declaring and Defining a Class | 85

In our example, we can create a class to represent GPS coordinates. The data will
be the two float variables to describe latitude and longitude.
Examples of operations could be ways to compute distances between coordinates,
or to check whether a coordinate is inside a specific state. The programmer will
directly operate on the class and will not have to interact with the two float
variables that are used to represent it.
• Information hiding: The process of exposing a set of functionalities to the user of
the class while hiding the details of how they are implemented in the class.
This approach reduces the complexity of interacting with the class and makes it
easier to update the class implementation in the future:

Figure 2.1: The class exposes functionality that the user code uses directly, hiding the fact that it is
implemented with two floats

We discussed the fact that we can represent GPS coordinates as latitude and
longitude. Later, we might decide to represent a coordinate as the distance
from the North Pole. Thanks to information hiding, we can change how a class
is implemented and the users of the class will not be impacted, since we do not
change the functionality offered by the class:

Figure 2.2: The implementation of the class changes, but since it is hidden from the user and the
functionality was not changed, the user does not have to change how their code interacts with it
86 | Classes

The set of functionalities the class exposes to the users is normally referred to as
the public interface.

Note
Changing the implementation of a class is generally more convenient than to
changing the interface of a class, which requires you to change all the users of the
class to adapt to the new interface. Getting the design of the public interface of a
class right is the first step to creating a class that is easy to use and requires low
maintenance.

• Encapsulation: This is the principle of grouping the data and the operations we
can perform on it together. Since the data is hidden in the class, the user cannot
access or operate on it. The class must provide functionality to interact with it.
C++ enables encapsulation by letting the user put the operations to interact with
a class and the data that is used to implement such operations in the same unit:
class.

Let's explore the structure of a class in C++ and the information associated with it. The
following is the basic structure of a class:
class ClassName {
// class body
};

Note
It is common to forget the last semicolon after closing curly brackets. Always make
sure that you add it.

C++ Data Members and Access Specifiers


Inside the body of a class, we can define the following class members:
• Data members: These are variables that live inside a class, which look like a
variable declaration, but are inside the class body. They are also called fields.
• Member functions: These are functions that can access the variables inside a
class. They look like a function declaration but are inside the class body. They are
also called methods.
Declaring and Defining a Class | 87

As we mentioned before, classes support information hiding by denying users of the


class to access information. The programmer uses access specifiers to specify which
parts of the class are available for the user to be accessed.
There are the following three access specifiers in C++:
• Private: Members declared as private can only be accessed by the functions inside
the class and are not allowed to be accessed directly outside the class
• Protected: Members declared as protected can only be accessed by the functions
inside the class and the derived classes. We will learn more about in the last
chapter of this book
• Public: Members declared as public can be accessed from anywhere in the
program

Access specifiers followed by a colon delimit an area in the class, and any member
defined in that area has the access specifier that precedes it. Here's the syntax:
class ClassName {
private:
int privateDataMember;
int privateMemberFunction();

protected:
float protectedDataMember;
float protectedMemberFunction();

public:
double publicDataMember;
double publicMemberFunction();
};

Note
By default, class members have the private access modifier.
88 | Classes

In C++, we can also use the struct keyword to define a class. A struct is identical to a
class, with the only exception that, by default, the access modifier is public, while for
the class it is private.
The following side-by-side code snippets are equivalent:

Figure 3.2: The difference between the code snippets of class and struct

Whether to use struct or class depends on convention used: usually, we use structs
when we want a collection of data members that should be accessible from anywhere
in the code; on the other hand, we use classes when we are modelling a more complex
concept.
We have learned how to define a class. Now, let's understand how to use one in a
program.
A class defines a blueprint or the design of an object. Like a blueprint, we can create
multiple objects from the same class. These objects are called instances.
We can create an instance in the same way that we create any basic type: define the
type of the variable followed by the name of the variable. Let's explore the following
example.
class Coordinates {
public:
float latitude;
float longitude;

float distance(const Coordinates& other_coordinate);


};
Declaring and Defining a Class | 89

Here's an example that shows a class that has multiple instances:


Coordinates newYorkPosition;
Coordinates tokyoPosition;
Here, we have two instances of the Coordinates class, each with their latitude and
longitude, which can change independently. Once we have an instance, we can access
its members.
When we declare a class, we create a new scope called the class scope. The names
defined inside the class scope are accessible only inside the same class scope. The
operator for accessing members of a class or a struct from a scope outside the class is
the dot (.) operator.
For the previously defined variables, we can access their latitude using the following
code:
float newYorkLatitude = newYorkPosition.latitude;

If we want to call a member function instead, we can invoke it like this:


float distance = newYorkPosition.distance(tokyoPosition);

On the other hand, when we are writing the body of a class method, we are inside
the class's scope. This means that we can access the other members of the class by
using their names directly, without having to use the dot operator. The members of the
current instance are going to be used.
Let's assume that the distance method is implemented as follows:
float Coordinates::distance(const Coordinates& other_coordinate) {
return pythagorean_distance(latitude, longitude, other_coodinate.latitude,
other_coordinate.longitude);
}
When we call newYorkPosition.distance(tokyoPosition);, the distance method is
called on the newYorkPosition instance. This means that latitude and longitude in the
distance method refer to newYorkPosition.latitude and newYorkPosition.longitude,
while other_coordinate.latitude refers to tokyoPosition.latitude.
If we had called tokyoPosition.distance(newYorkPosition); instead, the current
instance would have been tokyoPosition, and latitude and longitude would have
referred to the tokyoPosition, and other_coordinate to newYorkPosition.
90 | Classes

Static Members
In the previous section, we learned that a class defines the fields and methods that
compose an object. It is like a blueprint, specifying what the object looks like, but it
does not actually build it. An instance is the object that's built from the blueprint that's
defined by the class. Instances contain data and we can operate on instances.
Imagine the blueprint of a car. It specifies the engine of the car and that the car will
have four wheels. The blueprint is the class of the car, but we cannot turn on and drive
a blueprint. A car that's built by following the blueprint is an instance of the class.
The built car has four wheels and an engine, and we can drive it. In the same way, an
instance of a class contains the fields that are specified by the class.
This means that the value of each field is connected to a specific instance of a class
and evolves independently from the fields of all the other instances. At the same time,
it also means that a field cannot exist without the associated instance: there would be
no object that would be able to provide the storage (the space in memory) to store the
value of the field!
However, sometimes, we want to share the same value across all instances. In those
cases, we can associate the field with the class instead of the instance by creating a
static field. Let's examine the following syntax:
class ClassName {
static Type memberName;
};
There will be only one memberName field, which is shared across all instances. Like any
variable in C++, memberName needs to be stored in memory. We cannot use the storage
of the instance object, since memberName is not associated with any specific instance.
memberName is stored in a similar way to a global variable.
Outside of the class in which the static variable is declared, in a .cpp file, we can define
the value of the static variable. The syntax to initialize the value is as follows:
Type ClassName::memberName = value;

Note
Note that we do not repeat the static keyword.

It is important to define the values of the static variables in the .cpp file. If we
define them inside the header file, the definition will be included anywhere inside
the header, which will create multiple definitions, and the linker will complain.
Declaring and Defining a Class | 91

A class static variable's lifetime lasts for the complete duration of the program, like
global variables.
Let's see an example of how a static field in a class can be defined in the header and
how to assign a value to it in the .cpp file:
// In the .h file
class Coordinates {
// Data member
float latitude_ = 0;

// Data member
float longitude_ = 0;

public:
// Static data member declaration
static const Coordinates hearthCenter;

// Member function declaration


float distanceFrom(Coordinates other);

// Member function definition


float distanceFromCenter() {
return distanceFrom(hearthCenter);
}
};

// In the .cpp file


// Static data member definition
const Coordinates Coordinates::hearthCenter = Coordinates(0, 0);

When accessing the members of an instance, we learned to use the dot operator.
92 | Classes

When accessing a static member, we might not have an instance to use the dot operator
on. C++ gives us the ability to access the static members of a class by using the scope
resolution operator, which is, a double colon (::), after the class name.

Note
Always use const when declaring a static field. Any instance can access the static
fields of its class; if they are mutable, it becomes extremely hard to track down
which instances is modifying the value. In programs that use multiple threads, it is
common to create bugs by modifying the static fields from different threads at the
same time.

Let's examine the following exercise to understand how static variables work.

Exercise 7: Working with Static Variables


Let's write a program to print and find the square of numbers from 1 to 10:
1. Include the required header files.
2. Write the squares() function and the following logic:
void squares()
{
static int count = 1;
int x = count * count;
x = count * count;
std::cout << count << "*" << count;
std::cout << ": " << x <<std::endl;
count++;
}
3. Now, in the main function, add the following code:
int main()
{
for (int i=1; i<11; i++)
squares();
return 0;
}
Member Functions | 93

The output is as follows:


1*1: 1
2*2: 4
3*3: 9
4*4: 16
5*5: 25
6*6: 36
7*7: 49
8*8: 64
9*9: 81
10*10: 100
In addition to static fields, classes can also have static methods.
A static method is associated with a class; it can be invoked without an instance. Since
the fields and members of a class are associated with an instance, while static methods
are not, static methods cannot invoke them. Static methods can be invoked using the
scope resolution operator: ClassName::staticMethodName();.

Note
Static methods can only call other static methods and static fields inside a class.

Member Functions
Member functions are functions that are used to manipulate the data members of a
class, and they define the properties and behavior of the objects of the class.
Declaring a member function is just a matter of declaring a function inside the body of a
class. Let's examine the following syntax:
class Car
{
public:
void turnOn() {}
};
94 | Classes

Member functions, like the data members of a class, can be accessed using the dot (.)
operator that's applied on the object:
Car car;
car.turnOn();
Let's understand how to declare a member function outside the class scope.

Declaring a Member Function


Member functions, like data members, must be declared inside the class. However, a
member function's implementation can be placed either inside or outside the class,
body.
The following is a definition of a member function outside of the class, scope. This is
done by using the scope resolution operator (::) to declare that the function that's
being referred to is a member of the class. In the class, body, the function is declared
with its prototype:
class Car
{
public:
void turnOn();
};

void Car::turnOn() {}

Using const Member Functions


The member functions of a class can be qualified as const, which means that the
function limits its access to be read-only. Moreover, a member function is required
to be const when it accesses const member data. So, const member functions are not
allowed to modify the state of an object or call another function that does so.
To declare a member function as const, we use the const keyword in the function
declaration after the function name and before its body:
const std::string& getColor() const
{
// Function body
}
Member Functions | 95

In addition to the overloading rules that we learned in the previous chapter that
member functions can be overloaded in their const-ness, which means that two
functions can have identical signatures except for one being const and the other not.
The const member function will be called when an object is declared const; otherwise,
the non-const function is called. Let's examine the following code:
class Car
{
std::string& getColor() {}
const std::string& getColor() const {}
};
Car car;
// Call std::string& getColor()
car.getColor();
const Car constCar;
// Call const Color& getColor() const
constCar.getColor();

Note
It is important to distinguish between a const function and a function returning a
const type. Both make use of the same const keyword, but in different places in
the function prototype. They express a different concept and are independent.

The following examples show three versions of the const function:


• The first one is a const member function
• The second returns a const reference
• The third one is a const function that returns a const reference:
type& function() const {}

const type& function() {}

const type& function() const {}


96 | Classes

The this Keyword


When the this keyword is used in the class context, it represents a pointer whose
value is the address of the object on which the member function is called. It can appear
within the body of any non-static member function.
In the following example, setColorToRed() and setColorToBlue() perform the same
action. Both set a data member, but the former uses the this keyword to refer to the
current object:
class Car
{
std::string color;

void setColorToRed()
{
this->color = "Red";
// explicit use of this
}

void setColorToBlue()
{
color = "Blue";
// same as this->color = "Blue";
}
};

Note
pointer->member is a convenient way to access the member of the struct pointed
by pointer. It is equivalent to (*pointer).member.
Member Functions | 97

Exercise 8: Creating a Program Using the this Keyword to Greet New Users
Let's write a program that asks users for their names and greets them with a welcoming
message:
1. First, include the required header files.
2. Then, add the following functions to print the required output:
class PrintName {
std::string name;

};
3. Now, let's complete the program with a closing message using the this keyword.
Define the following methods inside the previous class:
public:
void set_name(const std::string &name){
this->name = name;
}
void print_name() {
std::cout << this->name << "! Welcome to the C++ community :)" <<
std::endl;
}
4. Write the main function, as follows:
int main()
{
PrintName object;
object.set_name("Marco");
object.print_name();
}
The output is as follows:
Marco! Welcome to the C++ community :)

Note
A function argument that has the same name as a data member of a class can
shadow its visibility. In this case, the this keyword is required for disambiguation.
98 | Classes

Non-Member Class-Related Functions


Defined as functions or operations that conceptually belong to the interface of a class,
non-member class-related functions are not part of a class itself. Let's examine the
following example:
class Circle{
public:
int radius;
};

ostream& print(ostream& os, const Circle& circle) {


os << "Circle's radius: " << circle.radius;
return os;
}

The print function writes the radius of the circle on the given stream, which is most
commonly the standard output.

Activity 7: Information Hiding Through Getters and Setters


In this activity, you are being asked to define a class named Coordinates, which contains
two data members, and latitude and longitude, both of type float and not publicly
accessible.
There are four operations that are associated with the Coordinates class: set_latitude,
set_longitude, get_latitude, and get_longitude.

Note
The set_latitude and set_longitude operations are used to set the x and y
coordinates (also referred to as setters), while get_latitude and get_longitude
are used to retrieve them (sometimes called getters).
Constructors and Destructors | 99

Performing encapsulation using the member functions through getter and setters.
To perform this activity, follow these steps:
1. Define a class with the name Coordinates, with its members under a private
access specifier.
2. Add the four operations previously specified and make them publicly accessible by
preceding their declaration by the public access specifier.
3. The setters (set_latitude and set_longitude) should take a float as a parameter
and return void, while the getters do not take any parameters and return a float.
4. The four methods should now be implemented. The setters assign the given value
to the corresponding member they are supposed to set; the getters return the
values that are stored.

Note
The solution for this activity can be found on page 288.

Constructors and Destructors


Up until now, we have learned how to declare data members, how to use them in
functions with a public specifier, and how to access them. Now, let's explore how to set
a value to them.
In the following example, we'll declare a struct by the name of Rectangle, and set a
value to it as follows:
struct Rectangle {
int height;
int width;
};

Rectangle rectangle;

// What will the following print function print?


std::cout << "Height: " << rectangle.height << std::endl;
100 | Classes

This line will print a random value because we never set the value of int. The C++ rule
for the initialization of basic types is that they get non-specified values.

Note
In some situations, the values of variables are set to 0 when they are not initialized.
This might happen because of some details in the implementation of the operating
system, the standard library, or the compiler, and the C++ standard does not
guarantee it. A program will have strange bugs when it relies on this behavior,
since it is unpredictable when variables are initialized to 0. Always explicitly
initialize variables with basic types.

Constructors
The way to initialize data members is by using a constructor. A constructor is a special
member function that has the same name as the class and no return type, and it is called
automatically by the compiler when a new object of the class is created.
Like any other function, a constructor can accept parameters and has a function body.
We can invoke a constructor by adding a parameter list after the name of the variable:
Rectangle rectangle(parameter1, paramter2, ..., parameterN);

When there are no parameters, we can avoid using parentheses, which is what we did in
the previous example.
An example of a constructor with no parameters for the Rectangle struct would look as
follows:
struct Rectangle {
int height, width;
Rectangle() {
height = 0;
width = 0;
}
};

Note
When the only operation the constructor does is initialize the data members, opt
for using the initialization list, which we will show you later in this chapter.
Constructors and Destructors | 101

In addition to assigning values to data members, a constructor can also execute


code, similar to a normal function body. This is important for the concept of the class
invariant.
A key advantage of hiding the implementation of a class in private members and only
exposing public methods to interact with the concept represented by the class is the
ability to enforce a class invariant.
A class invariant is a property or a set of properties of a class that should be true for
any given instance of the class, at any point. It is called invariant because the set of
properties do not vary; they are always true.
Let's look at an example of a class that requires a class invariant. Imagine that we want
to create a class that represents a date. The date would contain a year, month, and day,
all represented as integers.
Implement it as a struct with all the fields as public. Refer to the following code:
struct Date {
int day;
int month;
int year;
};
Now, the user could easily do the following:
Date date;
date.month = 153;
The previous code does not make any sense, as there are only 12 months in the
Gregorian calendar.
A class invariant for the date would be that the month is always between 1 and 12, and
that the day is always between 1 and 31, and depending on the month, even less.
Independently of any change the user performs on the Date object, the invariant must
always hold.
A class can hide the detail that the date is stored as three integers and expose the
functions to interact with the Date object. Functions can expect to find the dates to
always be in a valid state (the invariant is satisfied at the start of the function), and they
need to make sure to leave the class in a valid state (the invariant is satisfied at the end
of the function).
102 | Classes

The constructor does not only initialize the data members but also ensure that the class
respects the invariant. After the constructor is executed, the invariant must be true.

Note
The concept of an invariant is not specific to the C++ language, and there is no
dedicated facility to specify the invariant of a class. A best practice is to document
the expected invariant of the class together with the class code so that the
developers working with the class can easily check what the expected invariant is
and make sure they respect it.

Using assertions in code also helps in identifying when the invariant is not respected.
This probably means there is a bug in the code.

Overloading Constructor
Similar to other functions, we can overload the constructor by accepting different
parameters. This is useful when an object can be created in several ways, since the user
can create the object by providing the expected parameter, and the correct constructor
is going to be called.
We showed an example of a default constructor for the Rectangle class earlier in this
chapter. If we want to add a constructor that creates a rectangle from a square, we
could add the following constructor to the Rectangle class:
class Rectangle {
public:
Rectangle(); // as before
Rectangle (Square square);
};
The second constructor is an overloaded constructor and will be invoked according to
the way the class object is initialized.
Constructors and Destructors | 103

In the following example, the first line will call the constructor with empty parameters,
while the second line will call the overloaded constructor:
Rectangle obj; // Calls the first constructor
Rectangle obj(square); // Calls the second overloaded constructor

Note
A constructor with a single non-default parameter is also called a converting
constructor. This kind of constructor specifies an implicit conversion, from the
type of the argument to the class type.

The following conversion is possible according to the previous definitions:


Square square;
Rectangle rectangle(square);
The constructor is initialized, and it converts from type Square to Rectangle.
Similarly, the compiler can create implicit conversions when calling functions, as shown
in the following example:
void use_rectangle(Rectangle rectangle);

int main() {
Square square;
use_rectangle(square);
}

When calling use_rectangle, the compiler creates a new object of type Rectangle by
calling the conversion constructor, which accepts a Square.
One way to avoid this is to use the explicit specifier before the constructor definition:
explicit class_name(type arg) {}

Let's look at a different implementation of Rectangle, which has an explicit constructor:


class ExplicitRectangle {
public:
explicit ExplicitRectangle(Square square);
};
104 | Classes

When we use try to use Square to call a function that takes ExplicitRectangle, we get an
error:
void use_explicit_rectangle(ExplicitRectangle rectangle);

int main() {
Square square;
use_explicit_rectangle(square); // Error!
}

Constructor Member Initialization


Constructors, as we've seen already, are used to initialize members. Up until now, we
have initialized the members inside the body of the function by assigning values to
members directly. C++ provides a feature to initialize the values of fields of the class
in a more ergonomic way: initialization lists. Initialization lists allow you to call the
constructor of the data members of class before the constructor body is executed. To
write an initializer list, insert a colon (:) and a comma-separated list of initializations for
class members before the constructor's body.
Let's look at the following example:
class Rectangle {
public:
Rectangle(): width(0), height(0) { } //Empty function body, as the
variables have already been initialized
private:
int width;
int height;
};

Note how, in this last case, the constructor does nothing other than initialize its
members. Hence, it has an empty function body.
Now, if we try to print the width and the height of the Rectangle object, we will notice
that they are correctly initialized to 0:
Rectangle rectangle;
std::cout << "Width: " << rectangle.width << std::endl; // 0
std::cout << "Height: " << rectangle.height << std::endl; // 0
Constructors and Destructors | 105

Initializer lists are the recommended way to initialize member variables in C++, and they
are necessary when a data member is const.
When using an initializer list, the order in which the members are constructed is the
one in which they are declared inside the class; not the one in which they appear in the
initializer list. Let's look at the following example:
class Example {
Example() : second(0), first(0) {}
int first;
int second;
};
When calling the default constructor of the Example class, the first method will be
initialized first, and the second method after it, even if they appear in a different order in
the initializer list.

Note
You should always write the members in the initializer list in the same order as
they are declared; compilers will help you by warning you when the order differs
from the expected one.

Aggregate Classes Initialization


Classes or structs with no user-declared constructors, no private or protected
specifiers non-static data members, no base classes, and no virtual functions are
considered aggregate.

Note
We will talk about base classes and virtual functions in chapter 6.
106 | Classes

These types of classes can be initialized, even though they do not have a constructor, by
using a brace-enclosed comma-separated list of initializer-clauses, as shown here:
struct Rectangle {
int length;
int width;
};

Rectangle rectangle = {10, 15};

std::cout << rectangle.length << "," << rectangle.width;


// Prints: 10, 15

Destructors
A destructor function is called automatically when the object goes out of scope and is
used to destroy objects of its class type.
Destructors have the same name as the class preceded by a tilde (~) and do not take any
argument nor return any value (not even void). Let's examine the following example:
class class_name {
public:
class_name() {} // constructor
~class_name() {} // destructor
};
After executing the body of the destructor and destroying any automatic objects
allocated within the body, a destructor for a class calls the destructors for all the
direct members of the class. Data members are destroyed in reverse order of their
construction.
Constructors and Destructors | 107

Exercise 9: Creating a Simple Coordinate Program to Demonstrate the Use of


Constructors and Destructors
Let's write a simple program to demonstrate the use of constructors and destructors:
1. First, include the required header files.
2. Now, add the following code to the Coordinates class:
class Coordinates {
public:
Coordinates(){
std::cout << "Constructor called!" << std::endl;
}

~Coordinates(){
std::cout << "Destructor called!" << std::endl;
}

};
3. In the main function, add the following code:
int main()
{

Coordinates c;
// Constructor called!
// Destructor called!

}
The output is as follows:
Constructor called!
Destructor called!
108 | Classes

Default Constructor and Destructor


All the classes needs constructor and destructor functions. When the programmer does
not define these, the compiler automatically creates an implicitly defined constructor
and destructor.

Note
The default constructor might not initialize data members. Classes that have
members of a built-in or compound type should ordinarily either initialize those
members inside the class or define their version of the default constructor.

Activity 8: Representing Positions in a 2D Map


Alice is building a program to show 2D maps of the world. Users need to be able to
save locations, such as their house, a restaurant, or their workplace. To enable this
functionality, Alice needs to be able to represent a position in the world.
Create a class named Coordinates whose data members are the 2D coordinates of a
point. To ensure that the object is always properly initialized, implement a constructor
to initialize the data members of the class.
Let's perform the following steps:
1. The first step is to create a class named Coordinates containing the coordinates as
data members.
2. Now, there are two floating-point values, _latitude and _longitude, which identify
the coordinates on a geographic coordinate system. Additionally, these data
members are defined with the private access specifier.
3. Extend the class with a public constructor that takes two arguments, latitude and
longitude, which are used to initialize the data members of the class.
Resource Acquisition Is Initialization | 109

4. Alice can now use this Coordinates class to represent 2D positions on the map.

Note
The solution for this activity can be found on page 289.

Resource Acquisition Is Initialization


Resource Acquisition Is Initialization, or just RAII, is a programming idiom that is used
to manage the life cycle of a resource automatically by binding it to the lifetime of an
object.
Through the smart use of the constructor and destructor of an object, you can achieve
RAII. The former acquires the resource, while the latter takes care of realizing it. The
constructor is allowed to throw an exception, when a resource cannot be acquired,
while the destructor must never throw exceptions.
Typically, it is a good practice to operate on a resource via an instance of a RAII class
when its usage involves open()/close(), lock()/unlock(), start()/stop(), init()/
destroy(), or similar function calls.
The following is a way to open and close a file using an RAII-style mechanism.

Note
C++, like many languages, represents input/output operations as streams, where
data can be written to or read from.

The constructor of the class opens the file into a provided stream, while the destructor
closes it:
class file_handle {
public:
file_handle(ofstream& stream, const char* filepath) : _stream(stream) {
_stream.open(filepath);
}

~file_handle {
_stream.close();
110 | Classes

}
private:
ofstream& _stream;
};

To open the file, it is sufficient to provide the file path to the file_handle class. Then,
for the entire lifetime of the file_handle object, the file will not be closed. Once the
object reaches the end of the scope, the file is closed:
ofstream stream;
{
file_handle myfile(stream, "Some path"); // file is opened
do_something_with_file(stream);
} // file is closed here

This is used instead of the following code:


ofstream stream;
{
stream.open("Some path"); // file is opened
do_something_with_file(stream);
stream.close(); // file is closed here
}

Even though the benefit provided by applying the RAII idiom seems to be just to reduce
code, the real improvement is having safer code. It is common for a programmer to
write a function that correctly opens a file but never closes it or allocates memory that
never gets destroyed.
RAII makes sure that these operations cannot be forgotten, as it automatically handles
them.
Nested Class Declarations | 111

Activity 9: Storing Multiple Coordinates of Different Positions on a Map


In the 2D map program, the user can save multiple positions on the map. We need to be
able to store multiple coordinates to keep track of the positions saved by the user. To
do so, we need a way to create an array that can store them:
1. Using the RAII programming idiom, write a class that manages memory allocation
and the deletion of an array of values . The class has an array of integers as
member data, which will be used to store the values .
2. The constructor takes the size of the array as a parameter.
3. The constructor also takes care of allocating memory, which is used to store the
coordinates.
4. To allocate the memory use the function allocate_memory (number of elements)
which returns a pointer to an array of Coordinates of the requested size.
To release the memory, call release_memory (array) which takes an array of
Coordinates and releases the memory.
5. Finally, define a destructor and make sure to free the previously allocated array in
its implementation:

Note
The solution for this activity can be found on page 290.

Nested Class Declarations


Inside the scope of a class, we can declare more than just data members and member
functions; we can declare a class inside another class. These classes are called nested
classes.
Since a nested class declaration happens inside the outer class, it has access to all
the declared names as if it were part of the outer class: it can access even private
declarations.
On the other hand, a nested class is not associated with any instance, so it can only
access static members.
112 | Classes

To access a nested class, we can use the double colon (::), similar to accessing static
members of the outer class. Let's examine the following example:
// Declaration
class Coordinate {
...
struct CoordinateDistance {
float x = 0;
float y = 0;
static float walkingDistance(CoordinateDistance distance);
}
};
// Create an instance of the nested class CoordinateDistance
Coordinate::CoordinateDistance distance;

/* Invoke the static method walkingDistance declared inside the nested class
CoordinateDistance */
Coordinate::CoordinateDistance::walkingDistance(distance);
Nested classes are useful for two main reasons:
• When implementing a class, we need an object that manages some of the
logic of the class. In such cases, the nested class is usually private, and is not
exposed through the public interface of the class. It is mostly used to ease the
implementation of the class.
• When designing the functionality of a class, we want to provide a different class,
closely related to the original one, which provides part of that functionality. In that
case, the class is accessible by the users of the class and is usually an important
part of the interaction with the class.

Imagine a list – a sequence of objects. We would like the user to be able to iterate over
the items contained in the list. To do so, we need to keep track of which items the
user has already iterated over and which are remaining. This is typically done with an
iterator, which is a nested class. The iterator is an integral part of interacting with the
List class.
We will look at iterators more in detail in Lesson 5, Standard Library Containers and
Algorithms.
Friend Specifier | 113

Friend Specifier
As we have already seen, private and protected members of a class are not accessible
from within other functions and classes. A class can declare another function or class as
a friend: this function or class will have access to the private and protected members of
the class which declares the friend relationship.
The user has to specify the friend declaration within the body of the class.

Friend Functions
Friend functions are non-member functions that are entitled to access the private and
protected members of a class. The way to declare a function as a friend function is by
adding its declaration within the class and preceding it by the friend keyword. Let's
examine the following code:
class class_name {
type_1 member_1;
type_2 member_2;

public:
friend void print(const class_name &obj);
};

friend void print(const class_name &obj){


std::cout << obj.member_1 << " " << member_2 << std::endl;
}
In the previous example, the function declared outside of the class scope has the right
to access the class data members because it is declared as a friend function.

Friend Classes
Similarly, like a friend function, a class can also be made a friend of another class by
using the friend keyword.
114 | Classes

Declaring a class as a friend is like declaring all of its methods as friend functions.

Note
Friendship is not mutual. If a class is a friend of another, then the opposite is not
automatically true.

The following code demonstrates the concept of how friendship is not mutual:
class A {
friend class B;
int a = 0;
};

class B {
friend class C;
int b = 0;
};

class C {
int c = 0;

void access_a(const A& object) {


object.a;
// Error! A.a is private, and C is not a friend of A.
}
};

Friendship is not transitive; so, in the previous example, class C is not a friend of class A,
and the methods of class C cannot access the protected or private members of class A.
Additionally, A cannot access B's private members, since B is a friend of A, but friendship
is not mutual.
Friend Specifier | 115

Exercise 10: Creating a Program to Print the User's Height


Let's write a program that collects height input from the user in inches and, after
performing a calculation, prints the height of the user in feet:
1. First, let's add all the required header files to the program.
2. Now, create the Height class with one public method, as illustrated:
class Height {
double inches;
public:
Height(double value): inches(value) { }
friend void print_feet(Height);
};
3. As you can see, in the previous code, we used a friend function named
print_feet. Now, let's declare it:
void print_feet(Height h){
std::cout << "Your height in inches is: " << h.inches<< std::endl;
std::cout << "Your height in feet is: " << h.inches * 0.083 <<
std::endl;
}
4. Invoke the class in the main function, as shown here:
int main(){
IHeight h(83);
print_feet(h);
}
The output is as follows:
Your height in inches is: 83
Your height in feet is: 6.889
116 | Classes

Activity 10: The AppleTree Class, which Creates an Apple Instance


Sometimes, we would like to prevent the creation of an object of a specific type except
for a limited number of classes. This usually happens when the classes are strictly
related.
Create an Apple class that does not provide a public constructor and an AppleTree class
that is in charge of creating the former object.
Let's execute the following steps:
1. First, we need to create a class with a private constructor. In this way, the object
cannot be constructed, because the constructor is not publicly accessible:
class Apple
{
private:
Apple() {}
// do nothing
};
2. The AppleTree class is defined and contains a method called createFruit, which is
in charge of creating an Apple and returning it:
class AppleTree
{
public:
Apple createApple(){
Apple apple;
return apple;
}
};
3. If we compile this code, we will get an error. At this point, the Apple constructor is
private, so the AppleTree class cannot access it. We need to declare the AppleTree
class as a friend of Apple to allow AppleTree to access the private methods of
Apple:
class Apple
{
friend class AppleTree;
private:
Apple() {}
// do nothing
}
Copy Constructors and Assignment Operators | 117

4. The Apple object can now be constructed using the following code:
AppleTree tree;
Apple apple = tree.createFruit();

Note
The solution for this activity can be found on page 291.

Copy Constructors and Assignment Operators


One special type of constructor is the copy constructor. It initializes the data members
of one object to another object. The object that's used to copy the member's value is
passed as an argument to the copy constructor, typically of type reference to the class
itself, and possibly const qualified.
The following code refers to a class with a user-defined copy constructor, which copies
the data member of the other object into the current one:
class class_name {
public:
class_name(const class_name& other) : member(other.member){}

private:
type member;
};

A copy constructor is declared implicitly by the compiler when the class definition
does not explicitly declare a copy constructor and all the data members have a copy
constructor. This implicit copy constructor performs a copy of the class members in the
same order of initialization.
118 | Classes

Let's look at an example:


struct A {
A() {}
A(const A& a) {
std::cout << "Copy construct A" << std::endl;
}
};

struct B {
B() {}
B(const B& a) {
std::cout << "Copy construct B" << std::endl;
}
};

class C {
A a;
B b;
// The copy constructor is implicitly generated
};

int main() {
C first;
C second(first);
// Prints: "Copy construct A", "Copy construct B"
}
Copy Constructors and Assignment Operators | 119

When C is copy constructed, the members are copied in order: first, a is copied and
then b is copied. To copy A and B, the compiler calls the copy constructor defined in
those classes.

Note
When a pointer is copied, we are not copying the object pointed to, but simply the
address at which the object is located.

This means that when a class contains a pointer as a data member, the implicit
copy constructor only copies the pointer and not the pointed object, so the copied
object and the original one will share the object that's pointed to by the pointer.
This is sometimes called a shallow copy.

The copy Assignment Operator


An alternative way to copy an object is by using the copy assignment operator,
which, contrary to the construct operator, is called when the object has been already
initialized.
The assignment operator signature and implementation look quite similar to the copy
constructor, with the only difference being that the former is an overload of the =
operator and it generally returns a reference to *this, although it's not required.
Here's an example of the use of the copy assignment operator:
class class_name {
public:
class_name& operator= (const class_name & other) {
member = other.member;
}

private:
type member;
};
120 | Classes

Also, for the copy assignment operator, the compiler generates an implicit one when it
is not explicitly declared. As for the copy constructor, the members are copied in the
same order of initialization.
In the following example, the copy constructor and the copy assignment operator will
output a sentence when they are called:
class class_name {
public:
class_name(const class_name& other) : member(other.member){
std::cout << "Copy constructor called!" << std::endl;
}

class_name& operator= (const class_name & other) {


member = other.member;
std::cout << "Copy assignment operator called!" << std::endl;
}

private:
type member;
};
The following code shows two ways of copying an object. The former uses the
copy constructor, while the latter uses the copy assignment operator. The two
implementations will print a sentence when they are called:
class_name obj;

class_name other_obj1(obj);
\\ prints "Copy constructor called!"

class_name other_obj2 = obj;


\\ prints "Copy assignment operator called!"
Copy Constructors and Assignment Operators | 121

The move-constructor and move-assignment Operator


Like copying, moving also allows you to set the data members of an object to be equal to
those of another data member. The only difference with copying lies in the fact that the
content is transferred from one object to another, removing it from the source.
The move-constructor and move-assignment are members that take a parameter of
type rvalue reference to the class itself:
class_name (class_name && other);
// move-constructor

class_name& operator= (class_name && other);


// move-assignment

Note
For clarity, we can briefly describe an rvalue reference (formed by placing an &&
operator after the type of the function argument) as a value that does not have a
memory address and does not persist beyond a single expression, for example, a
temporary object.

A move constructor and a move assignment operator enable the resources owned by an
rvalue object to be moved into an lvalue without copying.
When we move a construct or assign a source object to a destination object, we transfer
the content of the source object into the destination object, but the source object needs
to remain valid. To do so, when implementing such methods, it is fundamental to reset
the data members of the source object to a valid value. This is necessary to prevent the
destructor from freeing the resources (such as memory) of the class multiple times.
Let's assume that there is a Resource that can be acquired, released, reset, and checked
if it's reset.
Here is an example of a WrongMove constructor:
class WrongMove {
public:
WrongMove() : _resource(acquire_resource()) {}

WrongMove(WrongMove&& other) {
_resource = other._resource;
122 | Classes

// Wrong: we never reset other._resource


}

~WrongMove() {
if (not is_reset_resource(_resource)) {
release_resource(_resource);
}
}

private:
Resource _resource;
}

The move-constructor of the WrongMove class will release the resource twice:
{
WrongMove first;
// Acquires the resource
{
/* Call the move constructor: we copy the resource to second, but we are
not resetting it in first */
WrongMove second(std::move(first));
}
/* Second is destroyed: second._resource is released here. Since we copied
the resource, now first._resource has been released as well. */
}
// First is destroyed: the same resource is released again! Error!

Instead, the move constructor should have reset the _resource member of other, so that
the destructor would not call release_resource again:
WrongMove(WrongMove&& other) {
_resource = other._resource;
other._resource = resetted_resource();
}
Copy Constructors and Assignment Operators | 123

The move constructor and move assignment operator can be implicitly generated by
the compiler if no user-defined ones are provided and there are no user-declared
destructors, copy constructors, or copy or move assignment operators:
struct MovableClass {
MovableClass(MovableClass&& other) {
std::cout << "Move construct" << std::endl;
}

MovableClass& operator=(MovableClass&& other) {


std::cout << "Move assign" << std::endl;
}
};

MovableClass first;
// Move construct
MovableClass second = std::move(first);
// Or: MovableClass second(std::move(first));
MovableClass third;
// Move assignment
second = std::move(third);

Preventing Implicit Constructors and Assignment Operators


The compiler will implicitly generate the copy constructor, copy assignment, move
constructor, and move assignment if our class respects all the required conditions.
For cases in which our class should not be copied or moved, we can prevent that.
To prevent the generation of implicit constructors and operators, we can write
the declaration of the constructor or operator and add = delete; at the end of the
declaration.
124 | Classes

Let's examine the following example:


class Rectangle {
int length;
int width;

// Prevent generating the implicit move constructor


Rectangle(Rectangle&& other) = delete;

// Prevent generating the implicit move assignment


Rectangle& operator=(Rectangle&& other) = delete;
};

Operator Overloading
C++ classes represent user-defined types. So, the need arises to be able to operate with
these types in a different way. Some operator functions may have a different meaning
when operating on different types. Operator overloading lets you define the meaning of
an operator when applied to a class type object.
For example, the + operator applied to numerical types is different than when it is
applied to the following Point class, which is constituted of coordinates. The language
cannot specify what the + operator should do for user-defined types such as Point,
as it is not in control of such types and does not know what the expected behavior is.
Because of that, the language does not define the operators for user-defined types.
However, C++ allows the user to specify the behavior of most operators for user-defined
types, including classes.
Here is an example of the + operator, defined for the Point class:
class Point
{
Point operator+(const Point &other)
{
Point new_point;
new_point.x = x + other.x;
new_point.y = y + other.y;
return new_point;
Operator Overloading | 125

}
private:
int x;
int y;
}
Here is a list of all the operators that can and cannot be overloaded:
• The following are the operators that can be overloaded:

Figure 3.4: Operators that can be overloaded

• The following are the operators that cannot be overloaded:

Figure 3.5: Operators that cannot be overloaded

Operators that expect two operands are called binary operators. Examples are +, -, *,
and /.
A method overloading a binary operator needs to accept a single parameter. When the
compiler encounters the use of the operator, it will call the method on the variable
on the left-hand side of the operator, while the variable on the right-hand side will be
passed as parameter to the method.
126 | Classes

We saw in the previous example that Point defines the + operator, which takes a
parameter. When using the addition operation on a Point, the code would look like this:
Point first;
Point second;
Point sum = first + second;
The last line from the code example is equivalent to writing the following:
Point sum = first.operator+(second);

The compiler automatically rewrites the first expression to the second one.
Operators that expect only one operand are called unary operators. Examples are --,
++, and !.
A method overloading a unary operator must not accept any parameters. When the
compiler encounters the use of the operator, it will call the method on the variable to
which the operator is assigned.
As an example, let's say we are given an object that's defined as follows:
class ClassOverloadingNotOperator {
public:
bool condition = false;

ClassOverloadingNotOperator& operator!() {
condition = !condition;
}
};
We would write the following:
ClassOverloadingNotOperator object;
!object;
Operator Overloading | 127

The code is therefore rewritten as follows:


ClassOverloadingNotOperator object;
object.operator!();

Note
Operator overloading is possible in two ways: either as a member function or as a
non-member function. The two end up producing the same effect.

Activity 11: Ordering Point Objects


In the 2D map application, we want to be able to display the locations that have been
saved by the user in order: from South-West to North-East. To be able to show the
locations in order, we need to be able to sort the location points representing the
locations in such an order.
Remember that the x coordinate represents the location along the West-East axis and
the y coordinate represents the location along the North-South axis.
In a real-life scenario, to compare two points, we need to compare their x and y
coordinates. To do so in code, we need to overload the < operator for the Point class.
This new function we're defining returns a bool, either true or false, according to the
order of p_1 and p_2.
The p_1 point comes before than p_2 in the order if the x coordinate of p_1 is less than
the x coordinate of p_2. If they are equal, then we need to compare their y coordinates.
Let's perform the following steps:
1. We need to add an overload for the < operator to the Point class that we
previously defined, which takes another object of type Point as an argument and
returns a bool indicating whether the object is less than the one provided as a
parameter, using the previous definition for how to compare two points:
2. At this point, we are able to compare two Point objects:
128 | Classes

3. Since, in our example, p_1.x is initialized to 1 and p_2.x to 2, the result of the
comparison will be true, which indicates that p_1 comes earlier than p_2 in the
order.

Note
The solution for this activity can be found on page 293.

Introducing Functors
A Functor (function object) is similar to a class. The class that overloads the operator()
function is also known as the function call operator.
The syntax that's used to define a functor is as follows:
class class_name {
public:
type operator()(type arg) {}
};

The function call operator has a return type and takes any number of arguments of
any type. To invoke the call operator of an object, we can write the name of the object,
followed by parentheses containing the arguments to pass to the operator. You can
imagine that an object that provides a call operator can be used in the same way as you
would use a function. Here's an example of a functor:
class_name obj;
type t;
/* obj is an instance of a class with the call operator: it can be used as
if it was a function */
obj(t);

They are particularly useful in places where you can pass a function object to an
algorithmic template that accepts an object with operator() defined. This exploits code
reusability and testability. We will see more on this in chapter 5 when we talk about
lambda.
Introducing Functors | 129

The following is a simple example of a functor that prints a string before appending a
new line at the end of it:
class logger{
public:
void operator()(const std::string &s) {
std::cout << s << std::endl;
}
};

logger log;
log ("Hello world!");
log("Keep learning C++");

Activity 12: Implementing Functors


Write a function object that takes a number when constructed and defines an operator
call that takes another number and returns the sum of the two.
Let's perform the following steps to achieve the desired output:
1. Define a class by the name of AddX, constituted by a private data member of type
int, and a constructor that is used to initialize it.
2. Extend it with the call operator, operator(), which takes an int as a parameter
and returns an int. The implementation in the function body should return the
addition of the previously defined x value and the parameter of the function
named y.
3. Instantiate an object of the class we just defined and invoke the calling operator:
class AddX {
public:
explicit AddX(int v) : value(v) {}
int operator()(int other_value) {
Indent it to the right, same as above
}
130 | Classes

private:
int value;
};

AddX add_five(5);
std::cout << add_five(4) << std::endl; // prints 9

Note
The solution for this activity can be found on page 294.

Summary
In this chapter, we saw how the concept of classes can be used in C++. We started by
delineating the advantages of using classes, describing how they can help us to create
powerful abstractions.
We outlined the access modifiers a class can use to control who has access to class
fields and methods.
We continued by exploring the conceptual differences between a class and its
instances, along with the implications this has when implementing static fields and
static methods.
We saw how constructors are used to initialize classes and their members, while
destructors are used to clean up the resources that are managed by a class.
We then explored how constructors and destructors can be combined to implement the
fundamental paradigm C++ is famous for: RAII. We showed how RAII makes it easy to
create classes that handle resources and make programs safer and easier to work with.
Finally, we introduced the concept of operator overloading and how it can be used to
create classes that are as easy to use as built-in types.
In the next chapter, we'll focus on templates. We'll primarily look at how to implement
template functions and classes, and write code that works for multiple types.
Generic Programming
4
and Templates

Lesson Objectives

By the end of this chapter, you will be able to:

• Understand how templates work and when to use them

• Identify and implement templated functions

• Implement template classes

• Write code that works for multiple types

In this chapter, you will learn how to use templates effectively in your program.
134 | Generic Programming and Templates

Introduction
When programming, it is common to face problems that are recurring for different
types of objects, such as storing a list of objects, or searching elements in a list, or
finding the maximum between two elements.
Let's say that in our program we want to be able to find the maximum between two
elements, either integers or doubles. With the features we have learned so far, we could
write the following code:
int max(int a, int b) {
if ( a > b) return a;
else return b;
}

double max(double a, double b) {


if ( a> b) return a;
else return b;
}

In the previous code, the two functions are identical except for the types of the
parameters and the return type. Ideally, we would like to write these kind of operations
only once and reuse them in the entire program.
Moreover, our max() function can only be called with types for which an overload exists:
int and double in this case. If we wanted it to work with any numerical type, we would
need to write an overload for each of the numerical types: we would need to know in
advance about all the types that will be used to call it, especially when the function
is part of a library that is intended to be used by other developers, as it becomes
impossible for us to know the types that will be used when calling the function.
We can see that there is nothing specific to integers being required to find the
maximum elements; if the elements implement operator<, then it is possible to find the
greater of the two numbers, and the algorithm does not change. In these situations, C++
offers an effective tool—templates.
Templates | 135

Templates
Templates are a way to define functions or classes that can work for many different
types, while still writing them only once.
They do so by having special kinds of parameters—type parameters.
When writing the template code, we can use this type parameter as if it were a real
type, such as int or string.
When the templated function is called or the template class is instantiated, the type
parameter is substituted with the real type that's used by the calling code.
Now let's look at an example of a template in C++ code:
template<typename T>
T max(T a, T b) {
if(a>b) {
return a;
} else {
return b;
}
}
A template always starts with the template keyword, followed by the list of template
parameters enclosed in angle brackets.
A template parameter list is a list of comma-separated parameters. In this case, we only
have one—typename T.
The typename keyword tells the template that we are writing a templated function that
uses a generic type, which we are going to name T.

Note
You can also use the class keyword in place of typename, since there is no
difference between them.
136 | Generic Programming and Templates

Then, the definition of the function follows. In the function definition, we can use the
name T when we want to refer to the generic type.
To call the template, we specify the name of the template, followed by the list of types
we want to use as type arguments, enclosed in angle brackets:
max<int>(10, 15);

This calls the templated function max, specifying int as the type parameter. We say that
we instantiated the templated function max with type int, and then called that instance.
We do not always need to specify the type parameters of a template; the compiler can
deduce them from the calling code. A later section will describe this feature.
Because of how powerful templates are, the big part of the C++ standard library is based
on templates, as we will see in Chapter 5, Standard Library Containers and Algorithms.
Now we'll explore in depth what happens when we compile the code that contains
templates.

Compiling the Template Code


Similar to functions and classes, a template needs to be declared before being used.
When the compiler first encounters a template definition in the program, it parses it
and performs only part of the checks it usually does on the rest of the code.
This happens because the compiler does not know which type is going to be used
with the template when it parses it, since the types are parameters themselves. This
prevents the compiler from performing checks that involve the parameter types, or
anything that depends on them.
Because of this, you get notified of some errors in the template only when you
instantiate it.
Once we define a template, we can instantiate it in our code.
When a template is instantiated, the compiler looks at the definition of the template
and uses it to generate a new instance of the code, where all the references to the type
parameters are replaced by the types that are provided when instantiating it.
Templates | 137

For example: when we call max<int>(1,2), the compiler looks at the template definition
we specified earlier and generates code as if we wrote the following:
int max(int a, int b) {
if(a>b) {
return a;
} else {
return b;
}
}

Note
Since the compiler generates the code from the template definition, it means that
the full definitions need to be visible to the calling code, not only the declaration,
as was the case for functions and classes.

The template can still be forward declared, but the compiler must also see the
definition. Because of this, when writing templates that should be accessed by several
files, both the definition and the declaration of the templates must be in the header file.
This restriction does not apply if the template is used only in one file.

Exercise 11: Finding the Bank Account of the User with the Highest Balance
Write a template function that accepts details of two bank accounts (of the same type)
and returns the balance of the bank account with the highest balance.
For this exercise, perform the following steps:
1. Let's create two structs named EUBankAccount and UKBankAccount to represent the
European Union bank account and the United Kingdom bank account with the
required basic information, as shown in the following code:
#include <string>
struct EUBankAccount {
std::string IBAN;
int amount;
};
138 | Generic Programming and Templates

struct UKBankAccount {
std::string sortNumber;
std::string accountNumber;
int amount;
};
2. The template function will have to compare the amount of the bank accounts. We
want to work with different bank account types, so we need to use a template:
template<typename BankAccount>
int getMaxAmount(const BankAccount& acc1, const BankAccount& acc2) {
// All bank accounts have an 'amount' field, so we can access it safely
if (acc1.amount > acc2.amount) {
return acc1.amount;
} else {
return acc2.amount;
}
}
3. Now, in the main function, call both the structs and the template function, as
shown here:
int main() {
EUBankAccount euAccount1{"IBAN1", 1000};
EUBankAccount euAccount2{"IBAN2", 2000};
std::cout << "The greater amount between EU accounts is " <<
getMaxAmount(euAccount1, euAccount2) << std::endl;

UKBankAccount ukAccount1{"SORT1", "ACCOUNT_NUM1", 2500};


UKBankAccount ukAccount2{"SORT2", "ACCOUNT_NUM2", 1500};
std::cout << "The greater amount between UK accounts is " <<
getMaxAmount(ukAccount1, ukAccount2) << std::endl;
}
The output is as follows:
The greater amount between EU accounts is 2000
The greater amount between UK accounts is 2500
Templates | 139

Using Template Type Parameters


As we saw earlier, the compiler uses the template as a guide to generate a template
instance with some concrete type when the template is used.
This means that we can use the type as a concrete type, including applying type
modifiers to it.
We saw earlier, a type can be modified by making it constant with the const modifier,
and we can also take a reference to an object of a specific type by using the reference
modifier:
template<typename T>
T createFrom(const T& other) {
return T(other);
}

Here, we can see a template function that creates a new object from a different instance
of an object.
Since the function does not modify the original type, the function would like to accept
it as a const reference.
Since we are declaring the type T in the template, in the function definition we can
use the modifiers on the type to accept the parameter in the way we deem more
appropriate.
Notice that we used the type two times: once with some modifiers and once with no
modifiers.
This gives a lot of flexibility when using templates and writing functions, as we can
liberally modify the type to suit our needs.
Similarly, we have a lot of freedom in where we can use the template arguments.
Let's see two templates with a multiple template type argument:
template<typename A, typename B>
A transform(const B& b) {
return A(b);
}
140 | Generic Programming and Templates

template<typename A, typename B>


A createFrom() {
B factory;
return factory.getA();
}
We can see that we can use the template argument in the function parameter, in the
return type, or instantiate it directly in the function body.
Also, the order in which the template arguments are declared does not impact where
and how the template parameters can be used.

Requirements of Template Parameter Types


In the code snippet at the beginning of this chapter, we wrote some templates that
accept any kind of type. In reality, our code does not work for any kind of type; for
example: max() requires the types to support the < operation.
We can see that there were some requirements on the type.
Let's try to understand what having a requirement on a type means when using
templates in C++ code. We will do so by using the following template code:
template<typename Container, typename User>
void populateAccountCollection (Container& container, const User& user) {
container.push_back(user.getAccount());
}

We can then write the following function as main and compile the program:
int main() {
// do nothing
}
When we compile this program, the compilation ends successfully without any error.
Templates | 141

Let's say we change the main function to be the following:


int main() {
std::string accounts;
int user;
populateAccountCollection(accounts, user);
}

Note
We did not specify the type to the template. We will see later in this chapter when
the compiler can automatically deduce the types from the call.

The compiler will give us an error when we compile it:


error: request for member 'getAccount' in 'user', which is of non-class type
'const int'

Note how the error appeared when we used the template function, and that it was not
detected before.
The error is telling us that we tried to call the getAccount method on an integer, which
does not have such a method.
Why didn't the compiler tell us this when we were writing the template?
The reason for this is that the compiler does not know what type User will be; therefore,
it cannot tell whether the getAccount method will exist or not.
When we tried to use the template, we tried to generate the code with two specific
types, and the compiler checked that these two types were suitable for the template;
they were not, and the compiler gave us an error.
The types we used were not satisfying the requirements of the template types.
Unfortunately, there is no easy way in the current C++ standard, even the most recent
C++17, to specify the requirements of templates in the code—for that, we need good
documentation.
142 | Generic Programming and Templates

The template has two type arguments, so we can look at the requirements for each
type:
• User requirements: The User object must have a getAccount method
• Container requirements: The Container object must have a push_back method

The compiler finds the first problem when we call the getAccount() function and it
notifies us.
To solve this issue, let's declare a suitable class, as shown here:
struct Account {
// Some fields
};
class User {
public:
Account getAccount() const{
return Account();
}
};

Now, let's call the template with the help of the following code:
int main() {
std::string accounts;
User user;
populateAccountCollection(accounts, user);
}
We still get an error:
error: no matching function for call to 'std::__cxx11::basic_
string<char>::push_back(Account)'

This time, the error message is less clear, but the compiler is telling us that there is no
method called push_back that accepts an account in basic_string<char> (std::string is
an alias for it). The reason for this is that std::string has a method called push_back, but
it only accepts characters. Since we are calling it with an Account, it fails.
Templates | 143

We need to be more precise in the requirements for our template:


• User requirements: The user object must have a getAccount method that returns
an object
• Container requirements: The container object must have a push_back method that
accepts objects of the type returned by getAccount on the user

Note
The std::vector type in the C++ standard library allows to store sequences of
elements of an arbitrary type. push_back is a method that's used for adding a
new element at the end of the vector. We will see more about vectors in Chapter 5,
Standard Library Containers and Algorithms.

We now change the calling code to consider all the requirements:


#include <vector>
int main(){
std::vector<Account> accounts;
User user;
populateAccountCollection(accounts, user);
}

This time, the code compiles correctly!


This shows us how the compiler checks most of the errors, but only when we
instantiate the template.
It is also very important to clearly document the requirements of the template so
that the user does not have to read complicated error messages to understand which
requirement is not respected.

Note
To make it easy to use our templates with many types, we should try to set the
least requirements we can on the types.
144 | Generic Programming and Templates

Defining Function and Class Templates


In the previous section, we saw the advantages of templates in writing abstractions. In
this section, we are going to explore how we can effectively use templates in our code
to create templated functions and templated classes.

Function Template
In the previous section, we learned how function templates are written.
In this section, we will learn about the two features that were introduced by C++11 that
make it easier to write template functions. These two functions are trailing return types
and decltype.
Let's start with the decltype. The decltype is a keyword that accepts an expression and
returns the type of that expression. Let's examine the following code:
int x;
decltype(x) y;
In the previous code, y is declared as an integer, because we are using the type of the
expression x, which is int.
Any expression can be used inside decltype, even complex ones, for example:
User user;
decltype(user.getAccount()) account;
Let's look at the second feature—trailing return types.
We saw that a function definition starts with the return type, followed by the name of
the function and then the parameters. For example:
int max(int a, int b);

Starting from C++11, it is possible to use a trailing return type: specifying the return type
at the end of the function signature. The syntax to declare a function with a trailing
return type is to use the keyword auto, followed by the name of the function and the
parameters, and then by an arrow and the return type.
The following is an example of a trailing return type:
auto max(int a, int b) -> int;

This is not beneficial when writing regular functions, but it becomes useful when
writing templates and when combined with decltype.
Defining Function and Class Templates | 145

The reason for this is that decltype has access to the variables defined in the
parameters of the function, and the return type can be computed from them:
template<typename User>
auto getAccount(User user) -> decltype(user.getAccount());
This is an example of a forward declaration of a function template.

Note
When the user wants to provide a definition, it needs to provide the same template
declaration, followed by the body of the function.

Without the trailing return type, we would have to know what the type returned
by user.getAccount() is to use it as the return type of the getAccount() function.
The return type of user.getAccount() can be different depending on the type of the
template parameter User, which in turn means that the return type of the getAccount
function could change depending on the User type. With the trailing return type, we
don't need to know what type is returned by user.getAccount(), as it is determined
automatically. Even better, when different types are used in our function or a user
changes the return type of the getAccount method in one of the types that's used to
instantiate the template, our code will handle it automatically.
More recently, C++14 introduced the ability to simply specify auto in the function
declaration, without the need for the trailing return type:
auto max(int a, int b)

The return type is automatically deduced by the compiler, and to do so, the compiler
needs to see the definition of the function—we cannot forward declare functions that
return auto.
Additionally, auto always returns a value—it never returns a reference: this is something
to be aware of when using it, as we could unintentionally create copies of the returned
value.
One last useful feature of function templates is how to reference them without calling
them.
Up until now, we have only seen how to call the function templates, but C++ allows us to
pass functions as parameters as well. For example: when sorting a container, a custom
comparison function can be provided.
146 | Generic Programming and Templates

We know that a template is just a blueprint for a function, and the real function is going
to be created only when the template is instantiated. C++ allows us to instantiate the
template function even without calling it. We can do this by specifying the name of the
template function, followed by the template parameters, without adding the parameters
for the call.
Let's understand the following example:
template<typename T>
void sort(std::array<T, 5> array, bool (*function)(const T&, const T&));
The sort is a function that takes an array of five elements and a pointer to the function
to compare two elements:
template<typename T>
bool less(const T& a, const T& b) {
return a < b;
}

To call sort with an instance of the less template for integers, we would write the
following code:
int main() {
std::array<int, 5> array = {4,3,5,1,2};
sort(array, &less<int>);
}

Here, we take a pointer to the instance of less for integers. This is particularly useful
when using the Standard Template Library, which we will see in Chapter 5, Standard
Library Containers and Algorithms.

Class Templates
In the previous section, we learned how to write template functions. The syntax for
class templates is equivalent to the one for functions: first, there is the template
declaration, followed by the declaration of the class:
template<typename T>
class MyArray {
// As usual
};
Defining Function and Class Templates | 147

And equivalently to functions, to instantiate a class template, we use the angle brackets
containing a list of types:
MyArray<int> array;

Like functions, class template code gets generated when the template is instantiated,
and the same restrictions apply: the definition needs to be available to the compiler and
some of the error-checking is executed when the template is instantiated.
As we saw in Lesson 3, Classes, while writing the body of a class, the name of the class
is sometimes used with a special meaning. For example, the name of the constructor
functions must match the name of the class.
In the same way, when writing a class template, the name of the class can be used
directly, and it will refer to the specific template instance being created:
template<typename T>
class MyArray {
// There is no need to use MyArray<T> to refer to the class, MyArray
automatically refers to the current template instantiation
MyArray();
// Define the constructor for the current template T

MyArray<T>();
// This is not a valid constructor.
};

This makes writing template classes a similar experience to writing regular classes, with
the added benefit of being able to use the template parameters to make the class work
with generic types.
Like regular classes, template classes can have fields and methods. The field can depend
on the type declared by the template. Let's review the following code example:
template<typename T>
class MyArray {
T[] internal_array;
};
148 | Generic Programming and Templates

Also when writing methods, the class can use the type parameter of the class:
template<typename T>
class MyArray {
void push_back(const T& element);
};

Classes can also have templated methods. Templated methods are similar to template
functions, but they can access the class instance data.
Let's review the following example:
template<typename T>
class MyArray {
template<typename Comparator>
void sort (const Comparator & element);
};
The sort method will accept any type and will compile if the type satisfies all the
requirements that the method imposes on the type.
To call the method, the syntax follows the one for calling functions:
MyArray<int> array;
MyComparator comparator;
array.sort<MyComparator>(comparator);

Note
The method template can be part of a non-template class.

In these situations, the compiler can sometimes deduce the type of the parameter,
where the user does not have to specify it.
Defining Function and Class Templates | 149

If a method is only declared in the class, as we did in the example with sort, the user
can later implement it by specifying the template types of both the class and the
method:
template<typename T> // template of the class
template<typename Comparator> // template of the method
MyArray<T>::sort(const Comparator& element) {
// implementation
}
The name of the types does not have to match, but it is a good practice to be consistent
with the names.
Similar to methods, the class can also have templated overloaded operators. The
approach is identical to writing the operator overloads for regular classes, with the
difference that the declaration of a template must precede the overload declaration like
we saw for method templates.
Finally, something to be aware of is how static methods and static fields interact with
the class template.
We need to remember that the template is a guide on the code that will be generated
for the specific types. This means that when a template class declares a static member,
the member is shared only between the instantiations of the template with the same
template parameters:
template<typename T>
class MyArray {
const Static int element_size = sizeof(T);
};
MyArray<int> int_array1;
MyArray<int> int_array2;
MyArray<std::string> string_array;
150 | Generic Programming and Templates

int_array1 and int_array2 will share the same static variable, element_size, since
they are both of the same type: MyArray<int>. On the other hand, string_array has
a different one, because its class type is MyArray<std::string>. MyArray<int> and
MyArray<std::string>, even if generated from the same class template, are two different
classes, and thus do not share static fields.

Dependent Types
It's fairly common, especially for code that interacts with templates, to define some
public aliases to types.
A typical example would be the value_type type alias for containers, which specifies
the type contained:
template<typename T>
class MyArray {
public:
using value_type = T;
};
Why is this being done?
The reason for this is that if we are accepting a generic array as a template parameter,
we might want to find out the contained type.
If we were accepting a specific type, this problem would not arise. Since we know the
type of vector, we could write the following code:
void createOneAndAppend(std::vector<int>& container) {
int new_element{}; // We know the vector contains int
container.push_back(new_element);
}

But how can we do this when we accept any container that provides the push_back
method?
template<typename Container>
void createOneAndAppend(Container& container) {
// what type should new_element be?
container.push_back(new_element);
}
Defining Function and Class Templates | 151

We can access the type alias declared inside the container, which specifies which kind
of values it contains, and we use it to instantiate a new value:
template<typename Container>
void createOneAndAppend(Container& container) {
Container::value_type new_element;
container.push_back(new_element);
}
This code, unfortunately, does not compile.
The reason for this is that value_type is a dependent type. A dependent type is a type
that is derived from one of the template parameters.
When the compiler compiles this code, it notices that we are accessing the value_type
identifier in the Container class.
That could either be a static field or a type alias. The compiler cannot know when
it parses the template, since it does not know what the Container type will be and
whether it has a type alias or a static variable. Therefore, it assumes we are accessing
a static value. If this is the case, the syntax we are using is not valid, since we still have
new_element{} after access to the field.
To solve this issue, we can tell the compiler that we are accessing a type in the class,
and we do so by prepending the typename keyword to the type we are accessing:
template<typename Container>
void createOneAndAppend(Container& container) {
typename Container::value_type new_element{};
container.push_back(new_element);
}

Activity 13: Reading Objects from a Connection


The user is creating an online game which require to send and receive its current state
over an internet connection. The application has several types of connections (TCP,
UDP, socket) each of them has a readNext() method which returns an std::array of 100
chars containing the data inside the connection, and a writeNext() method which takes
an std::array of 100 characters which puts data into the connection.
152 | Generic Programming and Templates

Let's follow these steps to create our online application:


1. The objects that the application wants to send and receive over the connection
have a serialize() static method which takes an instance of the object and return
an std::array of 100 characters representing the object.
class UserAccount {
public:
static std::array<char, 100> serialize(const UserAccount& account) {
std::cout << "the user account has been serialized" << std::endl;
return std::array<char, 100>();
}

static UserAccount deserialize(const std::array<char, 100>& blob) {


std::cout << "the user account has been deserialized" <<
std::endl;
return UserAccount();
}
};

class TcpConnection {
public:
std::array<char, 100> readNext() {
std::cout << "the data has been read" << std::endl;
return std::array<char, 100>{};
}
void writeNext(const std::array<char, 100>& blob) {
std::cout << "the data has been written" << std::endl;
}
};
2. The deserialize() static method takes an std::array of 100 characters
representing the object, and creates an object from it.
3. The connection objects are already provided. Create the header connection.h with
the following declarations:
template<typename Object, typename Connection>
Object readObjectFromConnection(Connection& con) {
std::array<char, 100> data = con.readNext();
return Object::deserialize(data);
}
Defining Function and Class Templates | 153

4. Write a function template called readObjectFromConnection that takes a connection


as the only parameter and the type of the object to read from the connection
as a template type parameter. The function returns an instance of the object
constructed after deserializing the data in the connection.
5. Then, call the function with an instance of the TcpConnection class, extracting an
object of type UserAccount:
TcpConnection connection;
UserAccount userAccount =
readObjectFromConnection<UserAccount>(connection);
The aim is to be able to send the information on the account of a user to the other users
connected to the same online game, so that they can see the user information like their
username and the level of their character.

Note
The solution for this activity can be found on page 295.

Activity 14: Creating a User Account to Support Multiple Currencies


Write a program that supports and stores multiple currencies. Follow these steps:
1. We want to create an Account class that stores the account balance in different
currencies.
2. A Currency is a class that represents a certain value in a specific currency. It
has a public field called value and a template function called to() that takes the
argument as a Currency type and returns an instance of that currency with the
value set to the appropriate conversion of the current value of the class:
struct Currency {
static const int conversionRate = CurrencyConversion;
int d_value;
Currency(int value): d_value(value) {}
};

template<typename OtherCurrency, typename SourceCurrency>


OtherCurrency to(const SourceCurrency& source) {
float baseValue = source.d_value / float(source.conversionRate);
int otherCurrencyValue = int(baseValue *
OtherCurrency::conversionRate);
return OtherCurrency(otherCurrencyValue);
154 | Generic Programming and Templates

using USD = Currency<100>;


using EUR = Currency<87>;
using GBP = Currency<78>;

template<typename Currency>
class UserAccount {
public:
Currency balance;
};
3. Our aim is to write an Account class that stores the current balance in any
currency provided by the template parameter.
4. The user account must provide a method called addToBalance that accepts any
kind of currency, and after converting it to the correct currency that's used for the
account, it should sum the value to the balance:
template<typename OtherCurrency>
void addToBalance(OtherCurrency& other) {
balance.value += to<Currency>(other).value;
}
5. The user now understands how to write class templates, how to instantiate them,
and how to call their templates.

Note
The solution for this activity can be found on page 296.

Non-Type Template Parameters


We learned how templates allow you to provide the types as parameters and how we
can make use of this to write generic code.
Templates in C++ have an additional feature—non-type template parameters.
A non-type template parameter is a template parameter that is not a type—it is a value.
We made use of such non-type template parameters many times when using
std::array<int, 10>;.
Non-Type Template Parameters | 155

Here, the second parameter is a non-type template parameter, which represents the
size of the array.
The declaration of a non-type template parameter is in the parameter list of the
template, but instead of starting with a typename keyword such as the type parameters,
it starts with the type of the value, followed by the identifier.
There are strict restrictions on the types that are supported as non-type template
parameters: they must be of integral type.
Let's examine the following example of the declaration of a non-type template
parameter:
template<typename T, unsigned int size>
Array {
// Implementation
};

For example: here, we declared a class template that takes a type parameter and a
non-type parameter.
We already saw that functions can take parameters directly and classes can accept
parameters in the constructor. Additionally, the type of regular parameters is not
restricted to be an integral type.
What is the difference between template and non-template parameters? Why would we
use a non-type template parameter instead of a regular parameter?
The main difference is when the parameter is known to the program. Like all the
template parameters and unlike the non-template parameters, the value must be known
at compile time.
This is useful when we want to use the parameters in expressions that need to be
evaluated at compile time, as we do when declaring the size of an array.
The other advantage is that the compiler has access to the value when compiling the
code, so it can perform some computations during compilation, reducing the amount of
instruction to execute at runtime, thus making the program faster.
Additionally, knowing some values at compile time allows our program to perform
additional checks so that we can identify problems when we compile the program
instead of when the program is executed.
156 | Generic Programming and Templates

Activity 15: Writing a Matrix Class for Mathematical Operations in a Game


In a game, it is common to represent the orientation of a character in a special kind of
matrix: a quaternion. We would like to write a Matrix class that will be the base of the
mathematical operations inside our game.
Our Matrix class should be a template that accepts a type, a number of rows, and a
number of columns.
We should store the elements of the matrix inside an std::array, stored inside the class.
The class should have a method called get() that takes a row and a column, and returns
a reference to the element in that position.
If the row or column is outside of the matrix, we should call std::abort().
Let's follow these steps:
1. The Matrix class takes three template parameters—one type and the two
dimensions of the Matrix class. The dimensions are of type int.
template<typename T, int R, int C>
class Matrix {
// We store row_1, row_2, ..., row_C
std::array<T, R*C> data;
public:
Matrix() : data({}) {}
};
2. Now, create a std::array with a size of the number of rows times the number of
columns so that we have enough space for all the elements of the matrix.
3. Add a constructor to initialize the array:
4. We add a get() method to the class to return a reference to the element T. The
method needs to take the row and column we want to access.
5. If the index are outside of the bounds of the matrix, we call std::abort(). In the
array, we store all the elements of the first row, then all the elements of the second
row, and so on. So, when we want to access the elements of the nth row, we need
to skip all the elements of the previous rows, which are going to be the number of
elements per row (so the number of columns) times the previous rows:
T& get(int row, int col) {
if (row >= R || col >= C) {
std::abort();
}
Non-Type Template Parameters | 157

return data[row*C + col];


}
The output is as follows:
Initial matrix:
1 2
3 4
5 6

Note
The solution for this activity can be found on page 298.

Bonus step:
In games, multiplying a matrix by a vector is a common operation.
Add a method to the class that takes a std::array containing elements of the same type
of the matrix, and returns a std::array containing the result of the multiplication. See
the definition of a matrix-vector product at https://mathinsight.org/matrix_vector_
multiplication.
Bonus step:
We add a new method, multiply, which takes a std::array of type T with the length of C
by const reference, since we are not modifying it.
The function returns an array of the same type, but a length of R?
We follow the definition of the matrix-vector multiplication to compute the result:
std::array<T, R> multiply(const std::array<T, C>& vector){
std::array<T, R> result = {};
for(int r = 0; r < R; r++) {
for(int c = 0; c < C; c++) {
result[r] += get(r, c) * vector[c];
}
}
return result;
}
158 | Generic Programming and Templates

Making Templates Easier to Use


We always said that we need to provide the template arguments to the parameters of a
template function or class. Now, in this section, we are going to see two features that
C++ offers to make it easier to use templates.
These features are default template arguments and template argument deduction.

Default Template Arguments


Like function arguments, template arguments can also have default values, both for
type and non-type template parameters.
The syntax for default template arguments is to add after the template identifier the
equal, followed by the value:
template<typename MyType = int>
void foo();
When a template provides a default value for a parameter, the user does not have to
specify the parameter when instantiating the template. The default parameter must
come after the parameters that do not have a default value.
Additionally, you can reference the previous template parameters when defining the
default type for a subsequent template parameter.
Let's see some examples of both errors and valid declarations:
template<typename T = void, typename A>
void foo();
• Error: The template parameter T, which has a default type, comes before the
template parameter A, which does not have a default parameter:
template<typename T = A, typename A = void>
void foo();
• Error: The template parameter T references the template parameter A, which
comes after T:
template<typename T, typename A = T >
void foo();
• Correct: A has a default value, and no other template parameter without default
value comes after it. It also references T, which is declared before the template
parameter A.
Making Templates Easier to Use | 159

The reason to use the default arguments is to provide a sensible option for the
template, but still allowing the user to provide their own type or value when needed.
Let's see an example of type arguments:
template<typename T>
struct Less {
bool operator()(const T& a, const T& b) {
return a < b;
}
};
template<typename T, typename Comparator= Less<T>>
class SortedArray;
The hypothetical type SortedArray is an array that keeps its elements always sorted. It
accepts the type of the elements it should hold and a comparator. To make it easy to
use for the user, it sets the comparator to use the less operator by default.
The following code shows how a user can implement it:
SortedArray<int> sortedArray1;
SortedArrat<int, Greater<int>> sortedArray2;
We can also see an example of a default non-type template parameter:
template<size_t Size = 512>
struct MemoryBuffer;
The hypothetical type MemoryBuffer is an object that reserves an amount of memory on
the stack; the program will then allocate objects into that memory. By default, it uses
512 bytes of memory, but the user can specify a different size:
MemoryBuffer<> buffer1;
MemoryBuffer<1024> buffer2;
Note the empty angle brackets in the buffer1 declaration. They are needed to signal to
the compiler that we are making use of a template. This requirement has been removed
in C++17, and we can write MemoryBuffer buffer1;.
160 | Generic Programming and Templates

Template Argument Deduction


All the template parameters need to be known to instantiate a template, but not all of
them need to be explicitly provided by the caller.
Template argument deduction refers to the ability of the compiler to automatically
understand some of the types that are used to instantiate the template, without the
user having to explicitly type them.
We are going to see them for functions as that is supported by most of the versions of
C++. C++17 introduced deduction guides, which allow the compiler to perform template
argument deduction for class templates from the constructor, but we are not going to
see them.
The detailed rules for template argument deduction are very complex, and so we are
going to see them by example so that we can understand them.
In general, the compiler tries to find the type for which the provided argument and the
parameter match the closest.
The code we are going to analyze is as follows:
template<typename T>
void foo(T parameter);
The calling code is as follows:
foo(argument);

Parameter and Argument Types


We are going to see how, based on different pairs of parameters and arguments, the
type is deduced:

Figure 4.1: Different parameter and argument types


Making Templates Easier to Use | 161

The error happens because we cannot bind a temporary value, like 1, to a non-const
reference.
As we can see, the compiler tries to deduce a type so that when it is substituted in the
parameter, it matches the argument as best as possible.
The compiler cannot always find such a type; in those situations, it gives an error and
it's up to the user to provide the type.
The compiler cannot deduce a type for any of the following reasons:
The type is not used in the parameters. For example: the compiler cannot deduce a type
if it is only used in the return type, or only used inside the body of the function.
The type in the parameter is a derived type. For example: template<typename T> void
foo(T::value_type a). The compiler cannot find the type T given the parameter that's
used to call the function.
Knowing these rules, we can derive a best practice for the order of the template
parameters when writing templates: the types that we expect the user to provide need
to come before the types that are deduced.
The reason for this is that a user can only provide the template arguments in the same
order they have been declared.
Let's consider the following template:
template<typename A, typename B, typename C>
C foo(A, B);
When calling foo(1, 2.23), the compiler can deduce A and B, but cannot deduce C. Since
we need all the types, and the user has to provide them in order, the user has to provide
all of the types: foo<int, double, and float>(1, 2.23);.
Let's say we put the types that cannot be deduced before the types that can be
deduced, as in the following example:
template< typename C, typename A, typename B>
C foo(A, B);
We could call the function with foo<float>(1, 2.23). We would then provide the type to
use for C and the compiler would automatically deduce A and B.
In a similar way, we need to reason about default template arguments.
Since they need to come last, we need to make sure to put the types that the user is
more likely to want to modify first, since that will force them to provide all the template
arguments up to that parameter.
162 | Generic Programming and Templates

Activity 16: Making the Matrix Class Easier to Use


The Matrix class we created in Activity 15: Writing a Matrix Class for Mathematical
Operations in a Game, requires that we provide three template parameters.
Now, in this activity, we want to make the class easier to use by requiring that the
user is required to only pass two parameters: the number of rows and the number
of columns in the Matrix class. The class should also take a third argument: the type
contained in the Matrix class. If not provided, it should default to int.
In the previous activity, we added to the matrix a multiply operation. We now want to
let the user customize the function by specifying how the multiplication between the
types should be executed. By default, we want to use the * operator. For that, a class
template named std::multiplies from the <functional> header exists. It works like the
Less class we saw previously in this chapter:
1. We start by importing <functional> so that we have access to std::multiplies.
2. We then change the order of the template parameters in the class template so that
the size parameters come first. We also add a new template parameter, Multiply,
which is the type we use for computing the multiplication between the elements
in the vector by default, and we store an instance of it in the class.
3. We now need to make sure that the multiply method uses the Multiply type
provided by the user to perform the multiplication.
4. To do so, we need to make sure we call multiplier(operand1, operand2) instead of
operand1 * operand2 so that we use the instance we stored inside the class:
std::array<T, R> multiply(const std::array<T, C>& vector) {
std::array<T, R> result = {};
for(int r = 0; r < R; r++) {
for(int c = 0; c < C; c++) {
result[r] += multiplier(get(r, c), vector[c]);
}
}
return result;
}
Being Generic in Templates | 163

5. Add an example of how we can use the class:


// Create a matrix of int, with the 'plus' operation by default
Matrix<3, 2, int, std::plus<int>> matrixAdd;
matrixAdd.setRow(0, {1,2});
matrixAdd.setRow(1, {3,4});
matrixAdd.setRow(2, {5,6});

std::array<int, 2> vector = {8, 9};


// This will call std::plus when doing the multiplication
std::array<int, 3> result = matrixAdd.multiply(vector);
The output is as follows:
Initial matrix:
1 2
3 4
5 6

Result of multiplication (with plus instead of multiply): [20, 24, 28]

Note
The solution for this activity can be found on page 300.

Being Generic in Templates


So far, we have learned how the compiler can make our templated functions easier to
use by automatically deducing the types used. The template code decides whether to
accept a parameter as a value or a reference, and the compiler finds the type for us. But
what do we do if we want to be agnostic regarding whether an argument is a value or a
reference, and we want to work with it regardless?
An example would be std::invoke in C++17. std::invoke is a function that takes a
function as the first argument, followed by a list of arguments, and calls the function
with the arguments. For example:
void do_action(int, float, double);
double d = 1.5;
std::invoke(do_action, 1, 1.2f, d);
164 | Generic Programming and Templates

Similar examples would apply if you wanted to log before calling a function, or you
wanted to execute the function in a different thread, such as std::async does.
Let's demystify the difference by using the following code:
struct PrintOnCopyOrMove {
PrintOnCopyOrMove(std::string name) : _name(name) {}
PrintOnCopyOrMove(const PrintOnCopyOrMove& other) : _name(other._name) {
std::cout << "Copy: " << _name << std::endl; }
PrintOnCopyOrMove(PrintOnCopyOrMove&& other) : _name(other._name) {
std::cout << "Move: " << _name << std::endl; }

std::string _name;
};
void use_printoncopyormove_obj(PrintOnCopyOrMove obj) {}

Note
use_printoncopyormove_obj always accepts the parameter by value.

Let's say we execute the following code:


PrintOnCopyOrMove local{"l-value"};
std::invoke(use_printoncopyormove_obj, local);
std::invoke(use_printoncopyormove_obj, PrintOnCopyOrMove("r-value"));
The code would print the following:
Copy: l-value
Move: r-value
How can we write a function such as std::invoke that works regardless of the kind of
reference (colloquially referred to as "ref-ness", similarly to how "const-ness" is used to
talk about whether a type is const qualified) of the parameters?
The answer to that is forwarding references.
Being Generic in Templates | 165

Forwarding references look like r-value references, but they only apply where the type
is deduced by the compiler:
void do_action(PrintOnCopyOrMove&&)
// not deduced: r-value reference
template<typename T>
void do_action(T&&) // deduced by the compiler: forwarding reference

Note
If you see a type identifier declared in the template, the type is deduced, and the
type has &&, then it is a forwarding reference.

Let's see how the deduction works for forwarding references:

Figure 4.2: Forward reference function.

Note
Let's say the type is not deduced, but, it is provided explicitly, for example:

int x = 0;

do_action<int>(x);

Here, T will be int, since it was explicitly stated.


166 | Generic Programming and Templates

The advantage, as we saw before, is that we work with any kind of reference, and when
the calling code knows it can move the object, then we can make use of the additional
performance provided by the move constructor, but when a reference is preferred, then
the code can use it as well.
Additionally, some types do not support copying, and we can make our template work
with those types as well.
When we write the body of the template function, the parameter is used as an l-value
reference, and we can write code ignoring whether T is an l-value reference or an
r-value one:
template<typename T>
void do_action(T&& obj) { /* forwarding reference, but we can access obj as
if it was a normal l-value reference */
obj.some_method();
some_function(obj);
}
In Chapter 3, Classes, we learned that std::move can make our code more efficient when
we need to use an object that we are not going to access after the call happens.
But we saw that we should never move objects we receive as an l-value reference
parameter, since the code that called us might still use the object after we return.
When we are writing templates using a forwarding reference, we are in front of a
dilemma: our type might be a value or a reference, so how do we decide whether we
can use std::move?
Does it mean we cannot make use of the benefit that std::move brings us?
The answer, of course, is no:
template<typename T>
void do_action(T&& obj) {
do_something_with_obj(???);
// We are not using obj after this call.
}
Should we use move or not in this case?
Being Generic in Templates | 167

The answer is yes: we should move if T is a value, and, no, we should not move if T is a
reference.
C++ provides us with a tool to do exactly this: std::forward.
std::forward is a function template that always takes an explicit template parameter
and a function parameter: std::forward<T>(obj).
Forward looks at the type of T, and if it's an l-value reference, then it simply returns
a reference to the obj, but if it's not, then it is equivalent to calling std::move on the
object.
Let's see it in action:
template<typename T>
void do_action(T&& obj) {
use_printoncopyormove_obj(std::forward<T>(obj));
}

Now, we call it by using the following code:


PrintOnCopyOrMove local{"l-value"};
do_action(local);
do_action(PrintOnCopyOrMove("r-value"));
do_action(std::move(local));
// We can move because we do not use local anymore
When executed, the code will print the following output:
Copy: l-val
Move: r-val
Move: l-val
We successfully managed to write code that is independent on whether the type is
passed as reference or value, removing a possible requirement on the template type
parameter.

Note
A template can have many type parameters. Forwarding references can apply to
any of the type parameters independently.
168 | Generic Programming and Templates

This is important because the caller of the templated code might know whether it
is better to pass values or pass references, and our code should work regardless of
whether there is a requirement to ask for a specific ref-ness.
We also saw how we can still maintain the advantages of moving, which is required
for some types that do not support copying. This can make our code run much faster,
even for types that support copying, without complicating our code: when we have
forwarding references we use std::forward where we would have used std::move.

Activity 17: Ensuring Users are Logged in When Performing Actions on the
Account
We want to allow the users of our e-commerce website to perform arbitrary actions
(for the scope of this activity, they will be adding and removing items) on their shopping
carts.
Before performing any action, we want to make sure that the user is logged in. Now,
let's follow these instructions:
1. Ensure that there is a UserIdentifier type for identifying the user, a Cart type that
represents the shopping cart of the user, and a CartItem type that represents any
item in the cart:
struct UserIdentifier {
int userId = 0;
};
struct Cart {
std::vector<Item> items;
};
2. Ensure that there is also a function with the signature bool isLoggedIn(const
UserIdentifier& user) and a function to retrieve the cart for an user, Cart
getUserCart(const UserIdentifier& user):
bool isLoggedIn(const UserIdentifier& user) {
return user.userId % 2 == 0;
}

Cart getUserCart(const UserIdentifier& user) {


return Cart();
}
Being Generic in Templates | 169

3. In most of our code, we only have access to the UserIdentifier for a user, and
we want to make sure that we always check whether the user is logged in before
doing any action on the cart.
4. To solve this problem, we decide to write a function template called execute_
on_user_cart, which takes the user identifier, an action, and a single parameter.
The function will check if the user is logged in and if so, retrieve their cart, then
perform the action of passing the cart and the single parameter:
template<typename Action, typename Parameter>
void execute_on_user_cart(UserIdentifier user, Action action, Parameter&&
parameter) {
if(isLoggedIn(user)) {
Cart cart = getUserCart(user);
action(cart, std::forward<Parameter>(parameter));
} else {
std::cout << "The user is not logged in" << std::endl;
}
}
5. One of the actions we want to perform is void remove_item(Cart,
CartItem). A second action we want to perform is void add_items(Cart,
std::vector<CartItem>):
void removeItem(Cart& cart, Item cartItem) {
auto location = std::find(cart.items.begin(), cart.items.end(),
cartItem);
if (location != cart.items.end()) {
cart.items.erase(location);
}
std::cout << "Item removed" << std::endl;
}

void addItems(Cart& cart, std::vector<Item> items) {


cart.items.insert(cart.items.end(), items.begin(), items.end());
std::cout << "Items added" << std::endl;
}

Note
A parameter of a function template can be used to accept functions as parameters.
170 | Generic Programming and Templates

The aim is to create a function that performs the necessary checks on whether the
user is logged in so that throughout our program we can use it to perform safely
any actions that are required by our business on the user cart, without the risk of
forgetting to check the logged status of the user.
6. We can also move the types that are not forwarding references:
template<typename Action, typename Parameter>
void execute_on_user_cart(UserIdentifier user, Action action, Parameter&&
parameter) {
if(isLoggedIn(user)) {
Cart cart = getUserCart(user);
action(std::move(cart), std::forward<Parameter>(parameter));
}
}
7. Examples of how the execute_on_user_cart function can be used with the actions
we described earlier in the activity is as follows:
UserIdentifier user{/* initialize */};
execute_on_user_cart(user, remove_item, CartItem{});
std::vector<CartItem> items = {{"Item1"}, {"Item2"}, {"Item3"}}; // might
be very long
execute_on_user_cart(user, add_items, std::move(items));
8. The developers in our software can write the functions they need to execute on
the cart, and call execute_on_user_cart to safely execute them.

Note
The solution for this activity can be found on page 302.

Variadic Templates
We just saw how we can write a template that accepts parameters independently from
their ref-ness.
But the two functions we talked about from the standard library, std::invoke and
std::async, have an additional property: they can accept any number of arguments.
In a similar way, std::tuple, a type similar to a std::array but that can contain values of
different types, can contain an arbitrary number of types.
Variadic Templates | 171

How is it possible for a template to accept an arbitrary number of arguments of


different types?
In the past, a solution to this problem was to provide a great number of overloads for
the same function, or multiple implementations of the class or struct, one for each
number of the parameters.
This is clearly code that is not easy to maintain, as it forces us to write the same code
multiple times. Another drawback is that there is a limit to the number of template
parameters, so if your code requires more parameters than what is provided, you do not
have a way to use the function.
C++11 introduced a nice solution for this problem: parameter pack.
A parameter pack is a template parameter that can accept zero or more template
arguments.
A parameter pack is declared by appending … to the type of the template parameter.
Parameter packs are a functionality that works with any template: both functions and
classes:
template<typename… Types>
void do_action();
template<typename… Types>
struct MyStruct;

A template that has a parameter pack is called a variadic template, since it is a template
that accepts a varying number of parameters.
When instantiating a variadic template, any number of arguments can be provided to
the parameter pack by separating them with a comma:
do_action<int, std:string, float>();
do_action<>();
MyStruct<> myStruct0;
MyStruct<float, int> myStruct2;

Types will contain the list of arguments that are provided when instantiating the
template.
A parameter pack by itself is a list of types and the code cannot interact with it directly.
The variadic template can use the parameter pack by expanding it, which happens by
appending … to a pattern.
172 | Generic Programming and Templates

When a pattern is expanded, it is repeated as many times as there are types in its
parameter pack, separating it with a comma. Of course, to be expanded, a pattern must
contain at least a parameter pack. If multiple parameters are present in the pattern, or
the same parameter is present several times, they are all expanded at the same time.
The simplest pattern is the name of the parameter pack: Types….
For example: to let a function accept multiple arguments, it would expand the
parameter pack in the function arguments:
template<typename… MyTypes>
void do_action(MyTypes… my_types);
do_action();
do_action(1, 2, 4.5, 3.5f);

When we call the function, the compiler automatically deduces the types of the
parameter pack. In the last call, MyTypes will contain int, double, and float, and the
signature of the generated function would be void do_action(int __p0, int __p1,
double __p2, float __p3).

Note
A parameter pack in the list of template parameters can only be followed by
template parameters that have a default value, or those that are deduced by the
compiler.

Most commonly, the parameter pack is the last in the list of template parameters.

The function parameter my_types is called a function parameter pack and needs to be
expanded as well so that it can access the single parameters.
For example: let's write a variadic struct:
template<typename… Ts>
struct Variadic {
Variadic(Ts… arguments);
};
Variadic Templates | 173

Let's write a function that creates the struct:


template<typename… Ts>
Variadic<Ts…> make_variadic(Ts… args) {
return Variadic<Ts…>(args…);
}

Here, we have a variadic function that takes a parameter pack and expands it when
calling the constructor of another variadic struct.
The function parameter packs, which is the function variadic parameter, can be
expanded only in some locations—the most common is as parameters when calling a
function.
The template parameter packs, which is a type variadic parameter, can be expanded
in template argument lists: the list of arguments between <> when instantiating a
template.
As we mentioned previously, the pattern for the expansion might be more complex than
just the name of the argument.
For example: we can access type aliases declared in the type or we can call a function
on the parameter:
template<typename… Containers>
std::tuple<typename Containers::value_type…> get_front(Containers…
containers) {
return std::tuple<typename Containers::value_type…>(containers.front()…);
}

We call it like so:


std::vector<int> int_vector = {1};
std::vector<double> double_vector = {2.0};
std::vector<float> float_vector = {3.0f};
get_front(int_vector, double_vector, float_vector) // Returns a tuple<int,
double, float> containing {1, 2.0, 3.0}
174 | Generic Programming and Templates

Alternatively, we can pass the parameter as an argument to a function:


template<typename… Ts>
void modify_and_call (Ts… args) {
do_things(modify (args)…));
}

This will call the modify function for each argument and pass the result to do_things.
In this section, we saw how the variadic parameter functionality of C++ lets us write
functions and classes that work with any number and type of parameters.
While it is not a common everyday task to write variadic templates, almost every
programmer uses a variadic template in their day-to-day coding, since it makes it so
much easier to write powerful abstractions, and the standard library makes vast use of
it.
Additionally, in the right situation, variadic templates can allow us to write expressive
code that works in the multitude of situations we need.

Activity 18: Safely Performing Operations on the User Cart with an Arbitrary
Number of Parameters
In the previous activity, we saw a function, execute_on_user_cart, which allows us to
execute arbitrary functions that take an object of type Cart and a single parameter.
In this activity, we want to expand on the supported types of actions we can perform on
the shopping cart of the user by allowing any function that takes an object of type Cart
and an arbitrary number of arguments:
1. Expand the previous activity to accept any number of the parameter with any kind
of ref-ness and pass it to the action provided.
2. Write variadic templates and learn how to expand them:
template<typename Action, typename... Parameters>
void execute_on_user_cart(UserIdentifier user, Action action,
Parameters&&... parameters) {
if(isLoggedIn(user)) {
Cart cart = getUserCart(user);
Writing Easy-to-Read Templates | 175

action(std::move(cart), std::forward<Parameters>(parameters)...);
}
}

Note
The solution for this activity can be found on page 303.

Writing Easy-to-Read Templates


Up until now, we have seen many features that we can use to write powerful templates
that allow us to create high-level abstractions over the specific problems we face.
But, as usual, code is more often read than written, and we should optimize for
readability: the code should express the intentions of the code more than what
operation is achieved.
Template code can sometimes make that hard to do, but there are a few patterns that
can help.

Type Alias
Type aliases allow the user to give a name to a type. They are declared with using name
= type.
After the declaration, everywhere Name is used is going to be equivalent to having used
Type.
This is very powerful for three reasons:
• It can give a shorter and more meaningful name to complex types
• It can declare a nested type to simplify access to it
• It allows you to avoid having to specify the typename keyword in front of a
dependent type
176 | Generic Programming and Templates

Let's see examples for these two points.


Imagine we have a type, UserAccount, which contains several fields on the user, such as
user ID, user balance, user email, and more.
We want to organize the user accounts into a high scoreboard based on their account
balances to visualize which users are most actively using our service.
To do so. we can use a data structure that requires a few parameters: the type to store,
a way for ordering the types, a way to compare the types, and possibly others.
The type could be as follows:
template<typename T, typename Comparison = Less<T>, typename Equality =
Equal<T>>
class SortedContainer;
To be easy to use, the template correctly provided some default values for Comparison
and Equality, which use the < and == operators, but our UserAccount type does not
implement the < operator, as there is no clear ordering, and the == operator does not
do what we want, as we are only interested in comparing balances. To solve this, we
implemented two structures to provide the functionality we need:
SortedContainer<UserAccount, UserAccountBalanceCompare,
UserAccountBalanceEqual> highScoreBoard;

The creation of a high scoreboard is both verbose.


Using a type alias, we could write the following:
using HighScoreBoard = SortedContainer<UserAccount,
UserAccountBalanceCompare, UserAccountBalanceEqual>;

Following this, we could create instances of HighScoreBoard directly, with little typing
and clearly specify the intent:
HighScoreBoard highScoreBoard;

We now also have a single place to update if we want to change the way in which we
want to sort the accounts. For example: if we also wanted to consider how long the user
has been registered in the service, we could change the comparator the comparator.
Every user of the type alias will be updated, without the risk of forgetting to update one
location.
Writing Easy-to-Read Templates | 177

Additionally, we clearly have a location where we can put the documentation on the
decision made for using the type we picked.

Note
When using type aliases, give a name that represents what the type is for, not how
it works. UserAccountSortedContainerByBalance is a not a good name because it
tells us how the type works instead of what its intention is.

The second case is extremely useful for allowing code to introspect the class, that is,
looking into some of the details of the class:
template<typename T>
class SortedContainer {
public:
T& front() const;
};
template<typename T>
class ReversedContainer {
public:
T& front() const;
}

We have several containers, which mostly support the same operations. We would like
to write a template function that takes any container and returns the first element,
front:
template<typename Container>
??? get_front(const Container& container);
How can we find out what type is returned?
178 | Generic Programming and Templates

A common pattern is to add a type alias inside the class, like so:
template<typename T>
class SortedContainer {
using value_type = T; // type alias
T& front() const;
};
Now the function can access the type of the contained element:
template<typename Container>
typename Container::value_type& get_front(const Container& container);

Note
Remember that value_type depends on the Container type, so it is a dependent
type. When we use dependent types, we must use the typename keyword in front.

This way, our code can work with any type that declares the nested type value_type.
The third use case, that is, to avoid having to type the typename keyword repeatedly, is
common when interacting with code that follows the previous pattern.
For example: we can have a class that accepts a type:
template<typename Container>
class ContainerWrapper {
using value_type = typename Container::value_type;
}

In the rest of the class, we can use value_type directly, without having to type typename
anymore. This allows us to avoid a lot of repetitions.
The three techniques can also be combined. For example: you can have the following:
template<typename T>
class MyObjectWrapper {
using special_type = MyObject<typename T::value_type>;
};
Summary | 179

Template Type Alias


The ability to create type aliases, as described in the previous part of this chapter, is
already very useful for improving the readability of our code.
C++ gives us the ability to define generic type aliases so that they can simply be reused
by the users of our code.
A template alias is a template that generates aliases.
Like all the templates we saw in this chapter, they start with a template declaration and
follow with the alias declaration, which can depend on the type that's declared in the
template:
template<typename Container>
using ValueType = typename Container::value_type;
A ValueType is a template alias that can be instantiated with the usual template syntax:
ValueType<SortedContainer> myValue;.
This allows the code to just use the alias ValueType whenever they want to access the
value_type type inside any container.
Template aliases can combine all the features of templates: they can accept multiple
parameters, accept non-type parameters, and even use parameter packs.

Summary
In this chapter, the students were introduced to templates in C++. We saw that
templates exist to create high-level abstractions that work independently from
the types of the objects at zero overhead at runtime. We explained the concept of
type requirements: the requirements a type must satisfy to work correctly with the
templates. We then showed the students how to write function templates and class
templates, mentioning dependent types as well, to give the students the tools to
understand a class of errors that happen when writing template code.
We then showed how templates can work with non-type parameters, and how
templates can be made easier to use by providing default template arguments, thanks to
template argument deduction.
We then showed the students how to write more generic templates, thanks to the
forwarding reference, std::forward, and the template parameter pack.
Finally, we concluded with some tools to make templates easier to read and more
maintainable.
In the next chapter, we will cover standard library containers and algorithms.
Standard Library
5
Containers and
Algorithms
Chapter Objectives

By the end of this chapter, you will be able to:

• Explain what iterators are

• Demonstrate the use of sequential containers, container adaptors, and associative


containers

• Understand and use unconventional containers

• Explain cases of iterator invalidation

• Discover algorithms implemented in the standard library

• Use user-defined operations on algorithms with lambda expressions


182 | Standard Library Containers and Algorithms

Introduction
The core of C++ is its Standard Template Library (STL), which represents a set of
important data structures and algorithms that facilitates the programmer's task and
improves code efficiency.
The components of the STL are parametric so that they can be reused and combined
in different ways. The STL is mainly made up of container classes, iterators, and
algorithms.
Containers are used to store collections of elements of a certain type. Usually, the
type of the container is a template parameter, which allows the same container class
to support arbitrary elements. There are several container classes, each of them with
different characteristics and features.
Iterators are used to traverse the elements of a container. Iterators offer the
programmer a simple and common interface to access containers of a different type.
Iterators are similar to raw pointers, which can also iterate through elements using the
increment and the decrement operators, or can access a specific element using the
de-reference (*) operator.
Algorithms are used to perform standard operations on the elements stored in the
containers. They use iterators to traverse the collections, since their interface is
common to all the containers, so that the algorithm can be agnostic about the container
it's operating on.
Algorithms treat functions as parameters that are provided by the programmer in
order to be more flexible in the operation that's being performed. It is common to see
an algorithm applied to a container of objects of a user-defined type. This algorithm,
to execute correctly, needs to know how to treat the object in detail. For this reason,
the programmer provides a function to the algorithm to specify the operations to be
executed on the objects.

Sequence Containers
Sequence containers, sometimes referred to as sequential containers, are a particular
class of containers where the order in which their elements are stored is decided by
the programmer rather than by the values of the elements. Every element has a certain
position that is independent of its value.
Sequence Containers | 183

The STL contains five sequence container classes:

Figure 5.1: Table presenting the sequence container classes and their descriptions

Array
The array container is a fixed-size data structure of contiguous elements. It recalls the
static array that we saw in Chapter 1, Getting Started:

Figure 5.2: Array elements are stored in contiguous memory


184 | Standard Library Containers and Algorithms

An array's size needs to be specified at compile time. Once defined, the size of the array
cannot be changed.
When an array is created, the size elements it contains are initialized next to each
other in memory. While elements cannot be added or removed, their values can be
modified.
Arrays can be randomly accessed using the access operator with the corresponding
element's index. To access an element at a given position, we can use the operator []
or the at() member function. The former does not perform any range checks, while the
latter throws an exception if the index is out of range. Moreover, the first and the last
element can be accessed using the front() and back() member functions.
These operations are fast: since the elements are contiguous, we can compute the
position in memory of an element given its position in the array, and access that
directly.
The size of the array can be obtained using the size() member function. Whether the
container is empty can be checked using the empty() function, which returns true if
size() is zero.
The array class is defined in the <array> header file, which has to be included before
usage.

Vector
The vector container is a data structure of contiguous elements whose size can be
dynamically modified: it does not require to specify its size at creation time:

Figure 5.3: Vector elements are contiguous, and their size can grow dynamically

The vector class is defined in the <vector> header file.


Sequence Containers | 185

A vector stores the elements it contains in a single section of memory. Usually, the
section of memory has enough space for more elements than the number of elements
stored in the vector. When a new element is added to the vector, if there is enough
space in the section of memory, the element is added after the last element in the
vector. If there isn't enough space, the vector gets a new, bigger section of memory
and copies all the existing elements into the new section of memory, then it deletes
the old section of memory. To us, it will seem like the size of the section of memory has
increased:

Figure 5.4: Memory allocation of vectors

When the vector is created, it is empty.


Most of the interface is similar to the array's, but with a few differences.
Elements can be appended using the push_back() function or inserted at a generic
position using the insert() function. The last element can be removed using pop_back()
or at a generic position using the erase() function.
186 | Standard Library Containers and Algorithms

Appending or deleting the last element is fast, while inserting or removing other
elements of the vector is considered slow, as it requires moving all the elements to
make space for the new element or to keep all the elements contiguous:

Figure 5.5: Elements being moved during insertions or deletions inside a vector

Vectors, just like arrays, allow efficient access of elements at random positions. A
vector's size is also retrieved with the size() member function, but this should not be
confused with capacity(). The former is the actual number of elements in the vector,
and the latter returns the maximum number of elements that can be inserted in the
current section of memory.
Sequence Containers | 187

For example, in the preceding diagram, initially, the array had a size of 4 and a capacity
of 8. So, even when an element had to be moved to the right, the vector's capacity
did not change, as we never had to get a new, bigger section of memory to store the
elements.
The operation of getting a new section of memory is called reallocation. Since
reallocation is considered an expensive operation, it is possible to reserve enough
memory for a given number of elements by enlarging a vector's capacity using the
reserve() member function. The vector's capacity can also be reduced to fit the number
of elements using the shrink_to_fit() function in order to release memory that is not
needed anymore.

Note
Vector is the most commonly used container for a sequence of elements and is
often the best one performance-wise.

Let's look at the following example to understand how vector::front() and


vector::back() work in C++:
#include <iostream>
#include <vector>
// Import the vector library

int main()
{
std::vector<int> myvector;

myvector.push_back(100);
// Both front and back of vector contains a value 100

myvector.push_back(10);
// Now, the back of the vector holds 10 as a value, the front holds 100

myvector.front() -= myvector.back();
// We subtracted front value with back
188 | Standard Library Containers and Algorithms

std::cout << "Front of the vector: " << myvector.front() << std::endl;
std::cout << "Back of the vector: " << myvector.back() << std::endl;
}

Output:
Front of the vector: 90
Back of the vector: 10

Deque
The deque container (pronounced deck) is short for "double-ended queue." Like vector,
it allows for fast, direct access of deque elements and fast insertion and deletion at
the back. Unlike vector, it also allows for fast insertion and deletion at the front of the
deque:

Figure 5.6: Deque elements can be added and removed at the start and the end

The deque class is defined in the <deque> header file.


Deque generally requires more memory than vector, and vector is more performant for
accessing the elements and push_back, so unless it is required to insert at the front,
vector is usually preferred.

List
The list container is a data structure of nonadjacent elements that can be dynamically
grown:

Figure 5.7: List elements are stored in different sections of memory, and have connecting links
Sequence Containers | 189

The list class is defined in the <list> header file.


Each element in the list has its memory segment and a link to its predecessor and its
successor. The structure containing the element, which is the link to its predecessor
and to its successor, is called a node.
When an element is inserted in a list, the predecessor node needs to be updated so that
its successor link points to the new element. Similarly, the successor node needs to be
updated so that its predecessor link points to the new element:

Figure 5.8: C is to be inserted between A and B. A's successor and B's predecessor link must be updated
to point to C (orange). C's link to the predecessor and successor are updated to points A and B (green)

When an element is removed from the list, we need to update the successor link of
the predecessor node to point to the successor of the removed node. Similarly, the
predecessor link of the successor node needs to be updated to point to the predecessor
of the removed node.
In the preceding diagram, if we were to remove C, we would have to update A's
successor to point to C's successor (B), and B's predecessor to point to C's predecessor
(A).
190 | Standard Library Containers and Algorithms

Unlike vectors, lists do not provide random access. Elements are accessed by linearly
following the chain of elements: starting from the first, we can follow the successor link
to find the next node, or from the last node we can follow the predecessor link to find
the previous node, until we reach the element we are interested into.
The advantage of list is that insertion and removal are fast at any position, if we
already know the node at which we want to insert or remove. The disadvantage of this
is that getting to a specific node is slow.
The interface is similar to a vector, except that lists don't provide operator[].

Forward-List
The forward_list container is similar to the list container, with the difference that its
nodes only have the link to the successor. For this reason, it is not possible to iterate
over a forward_list in backward order:

Figure 5.9: Forward-list elements are like List elements, but only have one-way connecting links

As usual, the forward_list class is defined in the <forward_list> header file.


The forward_list class does not even provide push_back() or size(). Inserting an
element is done using insert_after(), which is a variation of the insert() function,
where the new element is inserted after the provided position. The same idea applies
to element removal, which is done through erase_after(), which removes the element
after the provided position.

Providing Initial Values to Sequence Containers


All the sequence containers we have looked at are empty when they are first created.
When we want to create a container containing some elements, it can be repetitive to
call the push_back() or insert() functions repeatedly for each element.
Fortunately, all the containers can be initialized with a sequence of elements when they
are created.
Sequence Containers | 191

The sequence must be provided in curly brackets, and the elements need to be comma-
separated. This is called an initializer list:
#include <vector>
int main()
{
// initialize the vector with 3 numbers
std::vector<int> numbers = {1, 2, 3};
}

This works for any of the containers we have seen in this chapter.

Activity 19: Storing User Accounts


We want to store the account balance, stored as an int instance, for 10 users. The
account balance starts with 0. We then want to increase the balance of the first and last
user by 100.
These steps will help you complete the activity:
1. Include the header for the array class.
2. Declare an integer array of ten elements.
3. Initialize the array using the for loop. The size() operator to evaluate the size of
the array and the operator[] to access every position of the array.
4. Update the value for the first and last user.

Note
The solution for this activity can be found on page 304.

Now let’s do the same using a vector:


1. Include the vector header.
2. Declare a vector of integer type and reserve memory to store 100 users with resize
it to be able to contain 10 users.
3. Use a for loop to initialize the vector.

With this activity, we learned how we can store an arbitrary number of accounts.
192 | Standard Library Containers and Algorithms

Associative Containers
Associative containers are containers that allow for the fast lookup of elements.
Additionally, the elements are always kept in a sorted order. The order is determined
by the value of the element and a comparison function. The comparison function is
called a comparator, and by default this is the operator<, although the user can supply
a Functor (function object) as a parameter to specify how the elements should be
compared. The <functional> header contains many such objects that can be used to
sort the associative containers, like std::less or std::less.

Figure 5.10: Table presenting associative containers and their descriptions

Typically, associative containers are implemented as variations of binary trees,


providing fast element lookup by exploiting the logarithmic complexity of the
underlying structure.

Set and Multiset


A Set is a container that contains a unique group of sorted elements. A Multiset is
similar to Set, but it allows duplicate elements:

Figure 5.11: Set and Multiset store a sorted group of elements


Associative Containers | 193

Set and multiset have size() and empty() function members to check how many
elements are contained and whether any elements are contained.
Insertion and removal is done through the insert() and erase() functions. Because
the order of the elements is determined by the comparator, they do not take a position
argument like they do for sequential containers. Both insertion and removal are fast.
Since sets are optimized for element lookup, they provide special search functions. The
find() function returns the position of the first element equal to the provided value, or
the position past the end of the set when the element is not found. When we look for an
element with find, we should always compare it with the result of calling end() on the
container to check whether the element was found.
Let's examine the following code:
#include <iostream>
#include <set>
int main() {
std::set<int> numbers;
numbers.insert(10);

if (numbers.find(10) != numbers.end()) {
std::cout << "10 is in numbers" << std::endl;
}
}

Finally, count() returns the number of elements equal to the value provided.
The set and multiset classes are defined in the <set> header file.
Example of a set with a custom comparator:
#include <iostream>
#include <set>
#include <functional>

int main() {
std::set<int> ascending = {5,3,4,2,1};
std::cout << "Ascending numbers:";
for(int number : ascending) {
std::cout << " " << number;
}
std::cout << std::endl;
194 | Standard Library Containers and Algorithms

std::set<int, std::greater<int>> descending = {5,3,4,2,1};


std::cout << "Descending numbers:";
for(int number : descending) {
std::cout << " " << number;
}
std::cout << std::endl;
}
Output:
Ascending numbers: 1 2 3 4 5
Descending numbers: 5 4 3 2 1

Map and Multimap


Map and multimap are containers that manage key/value pairs as elements. The
elements are sorted automatically according to the provided comparator and applied to
the key: the value does not influence the order of the elements:

Figure 5.12: Map and multimap store a sorted group of keys, which is associated to a value
Associative Containers | 195

Map allows you to associate a single value to a key, while multimap allows you to
associate multiple values to the same key.
The map and multimap classes are defined in the <map> header file.
To insert values into a map, we can call insert(), providing a pair containing the key
and the value. Later in this chapter, we will see more about pairs. The function also
returns a pair, containing the position at which the element was inserted, and a Boolean
set to true if the element was inserted, or false if an element with the same key already
exists.
Once values are inserted into the map, there are several ways to look up a key/value
pair in a map.
Similar to set, map provides a find() function, which looks for a key in the map and
returns the position of the key/value pair if it exists, or the same result of calling end().
From the position, we can access the key with position->first and the value with
position->second:
#include <iostream>
#include <string>
#include <map>

int main()
{
std::map<int, std::string> map;
map.insert(std::make_pair(1, "some text"));
auto position = map.find(1);
if (position != map.end() ) {
std::cout << "Found! The key is " << position->first << ", the value
is " << position->second << std::endl;
}
}
An alternative to accessing a value from a key is to use at(), which takes a key and
returns the associated value.
196 | Standard Library Containers and Algorithms

If there is no associated value, at() will throw an exception.


A last alternative to get the value associated with a key is to use operator[].
The operator[] returns the value associated with a key, and if the key is not present, it
inserts a new key/value pair with the provided key, and a default value for the value.
Because operator[] could modify the map by inserting into it, it cannot be used on a
const map:
#include <iostream>
#include <map>
int main()
{
std::map<int, int> map;
std::cout << "We ask for a key which does not exists: it is default
inserted: " << map[10] << std::endl;
map.at(10) += 100;
std::cout << "Now the value is present: " << map.find(10)->second <<
std::endl;
}

Activity 20: Retrieving a User's Balance from their Given Username


We'd like to be able to quickly retrieve the balance of a user given their username.
To quickly retrieve the balance from the username, we store the balance inside a map,
using the name of the user as a key.
The name of the user is of type std::string, while the balance is an int. Add the balance
for the users Alice, Bob, and Charlie with a balance of 50 each. Then, check whether the
user Donald has a balance.
Finally, print the account balance of Alice:
1. Include the header file for the map class and the header for string:
#include <string>
#include <map>
#include <string>
2. Create a map with the key being std::string and the value being int.
Unordered Containers | 197

3. Insert the balances of the users inside the map by using insert and std::make_
pair. The first argument is the key, while the second one is the value:
balances.insert(std::make_pair("Alice",50));

4. Use the find function, providing the name of the user to find the position of the
account in the map. Compare it with end() to check whether a position was found.
5. Now, look for the account of Alice. We know Alice has an account, so there is no
need to check whether we found a valid position. We can print the value of the
account using ->second:
auto alicePosition = balances.find("Alice");
std::cout << "Alice balance is: " << alicePosition->second << std::endl;

Note
The solution for this activity can be found on page 305.

Unordered Containers
Unordered associative containers differ from associative containers in that the
elements have no defined order. Visually, unordered containers are often imagined as
bags of elements. Because the elements are not sorted, unordered containers do not
accept a comparator object to provide an order to the elements. On the other hand, all
the unordered containers depend on a hash function.
he user can provide a Functor (function object) as a parameter to specify how the keys
should be hashed:

Figure 5.13: Table presenting unordered containers and their descriptions


198 | Standard Library Containers and Algorithms

Typically, unordered containers are implemented as hash tables. The position in the
array is determined using the hash function, which given a value returns the position
at which it should be stored. Ideally, most of the elements will be mapped into different
positions, but the hash function can potentially return the same position for different
elements. This is called a collision. This problem is solved by using linked lists to chain
elements that map into the same position, so that multiple elements can be stored in
the same position. Because there might be multiple elements at the same position, the
position is often called bucket.
Implementing unordered containers using a hash table allows us to find an element
with a specific value in constant time complexity, which translates to an even faster
lookup when compared to associative containers:

Figure 5.14: When an element is added to the set, its hash is computed to decide in which bucket the
element should be added. The elements inside a bucket are stored as nodes of a list.
Container Adaptors | 199

When a key/value pair is added to the map, the hash of the key is computed to decide
in which bucket the key/value pair should be added:

Figure 5.15: Representation of computing the bucket of an element from the key, and storing the key/
value pair as nodes in a list.

Unordered associative containers and ordered associative containers provide the same
functionalities, and the explanations in the previous section apply to the unordered
associative containers as well. Unordered associative containers can be used to get
better performances when the order of the elements is not important.

Container Adaptors
Additional container classes that are provided by the STL library are container adaptors.
Container adaptors provide constrained access policies on top of the containers we
have looked at in this chapter.
200 | Standard Library Containers and Algorithms

Container adaptors have a template parameter that the user can provide to specify the
type of container to wrap:

Figure 5.16: Table presenting container adaptors and their descriptions

Stack
The stack container implements the LIFO access policy, where the elements are
virtually stacked one on the top of the other so that the last inserted element is always
on top. Elements can only be read or removed from the top, so the last inserted element
is the first that gets removed. A stack is implemented using a sequence container class
internally, which is used to store all the elements and emulate the stack behavior.
The access pattern of the stack data structure happens mainly through three core
member functions: push(), top(), and pop(). The push() function is used to insert an
element into the stack, top() used to access the element on top of the stack, and pop()
is used to remove the top element.
The stack class is defined in the <stack> header file.

Queue
The queue class implements the FIFO access policy, where the elements are enqueued
one after the other, so that elements inserted before are ahead of elements inserted
after. Elements are inserted at the end of the queue and removed at the start.
The interface of the queue data structure is composed of the push(), front(), back(),
and pop() member functions.
The push() function is used to insert an element into the queue(); front() and back()
return the next and last elements of the queue, respectively; the pop() is used to
remove the next element from the queue.
The queue class is defined in the <queue> header file.
Container Adaptors | 201

Priority Queue
Finally, the priority queue is a queue where the elements are accessed according to
their priority, in descending order (highest priority first).
The interface is similar to the normal queue, where push() inserts a new element and
top() and pop() access and remove the next element. The difference is in the way
the next element is determined. Rather than being the first inserted element, it is the
element that has the highest priority.
By default, the priority of the elements is computed by comparing the elements with
the operator<, so that an element that is less than another comes after it. A user-
defined sorting criterion can be provided to specify how to sort the elements by
priority in regard to their priority in the queue.
The priority queue class is also defined in the <queue> header file.

Activity 21: Processing User Registration in Order


When a user registers to our website, we need to process the registration form at the
end of the day.
We want to process the registration in reverse order of registration:
1. Assume that the class for the registration form is already provided:
struct RegistrationForm {
std::string userName;
};
2. Create a stack to store the users.
3. We want to store the user registration form when the user registers, as well as
process the registration at the end of the day. The function for processing the
form is provided:
void processRegistration(RegistrationForm form) {
std::cout << "Processing form for user: " << form.userName <<
std::endl;
}
4. Additionally, there are already two functions that are called when a user registers.
202 | Standard Library Containers and Algorithms

5. Fill the code inside the following two functions to store the user form and process
it:
void storeRegistrationForm(std::stack<RegistrationForm>& stack,
RegistrationForm form) {
}
void endOfDayRegistrationProcessing(std::stack<RegistrationForm>& stack) {
}
We'll see that the registration forms are processed in reverse order as the users are
registered.

Note
The solution for this activity can be found at page 306.

Unconventional Containers
Up until now, we've seen containers that are used to store groups of elements of the
same type.
The C++ standard defines some other types that can contain types but offer a different
set of functionalities from the containers we saw previously.
These types are as follows:
1. String
2. Pair and tuple
3. Optional
4. Variant

Strings
A string is a data structure that's used to manipulate mutable sequences of contiguous
characters. The C++ string classes are STL containers: they behave similarly to vectors,
but provide additional functionalities that ease the programmer to perform common
operations of sequences of characters easily.
Unconventional Containers | 203

There exist several string implementations in the standard library that are useful for
different lengths of character sets, such as string, wstring, u16string, and u32string. All
of them are a specialization of the basic_string base class and they all have the same
interface.
The most commonly used type is std::string.
All types and functions for strings are defined in the <string> header file.
A string can be converted into a null-terminating string, which is an array of characters
that terminate with the special null character (represented with '\0') via the use of the
data() or c_str() functions. Null-terminating strings, also called C-strings, are the way
to represent sequences of character in the C language and they are often used when
the program needs to interoperate with a C library; they are represented with the const
char * type and are the type of the literal strings in our programs.

Exercise 12: Demonstrating Working Mechanism of the c_str() Function


Let's examine the following code to understand how the c_str() function works:
1. First include the required header files as illustrated:
#include <iostream>
#include <string>
2. Now, in the main function add a constant char variable named charString with
capacity as 8 characters:
int main()
{
// Construct a C-string being explicit about the null terminator
const char charString[8] = {'C', '+', '+', ' ', '1', '0', '1', '\0'};
// Construct a C-string from a literal string. The compiler
automatically adds the \0 at the end
const char * literalString = "C++ Fundamentals";
// Strings can be constructed from literal strings.
std::string strString = literalString;
3. Use the c_str() function and assign the value of strString to charString2:
const char *charString2 = strString.c_str();
204 | Standard Library Containers and Algorithms

4. Print the charString and charString2 using the print function:


std::cout << charString << std::endl;
std::cout << charString2 << std::endl;
}
The output is as follows:
Output:
C++ 101
C++ Fundamentals
As for vectors, strings have size(), empty(), and capacity() member functions, but
there is an additional function called length(), which is just an alias for size().
Strings can be accessed in a character-by-character fashion using operator[] or the
at(), front(), and back() member functions:
std::string chapter = "We are learning about strings";
std::cout << "Length: " << chapter.length() << ", the second character is "
<< chapter[1] << std::endl;
The usual comparison operators are provided for strings, thus simplifying the way two
string objects can be compared.
Since strings are like vectors, we can add and remove characters from them.
Strings can be made empty by assigning an empty string, by calling the clear(), or
erase() functions.
Let's look at the following code to understand the usage of the clear() and erase()
functions:
#include <iostream>
#include <string>

int main()
{
std::string str = "C++ Fundamentals.";
std::cout << str << std::endl;

str.erase(5,10);
Unconventional Containers | 205

std::cout << "Erased: " << str << std::endl;

str.clear();
std::cout << "Cleared: " << str << std::endl;
}

Output:
C++ Fundamentals.
Erased: C++ Fs.
Cleared:

C++ also provides many convenience functions to convert a string into numeric values
or vice versa. For example, the stoi() and stod() functions (which stand for string-
to-int and string-to-double) are used to convert string to int and double, respectively.
Instead, to convert a value into a string, it is possible to use the overloaded function
to_string().
Let's demystify these functions using the following code:
#include <iostream>
#include <string>
using namespace std;

int main()
{
std::string str = "55";
std::int strInt = std::stoi(str);
double strDou = std::stod(str);
std::string valToString = std::to_string(strInt);

std::cout << str << std::endl;


std::cout << strInt << std::endl;
std::cout << strDou << std::endl;
std::cout << valToString << std::endl;
}
206 | Standard Library Containers and Algorithms

Output:
55
55
55
55

Pairs and Tuples


The pair and tuple classes are similar to some extent, in the way they can store a
collection of heterogeneous elements.
The pair class can store the values of two types, while the tuple class extended this
concept to any length.
Pair is defined in the <utility> header, while tuple is in the <tuple> header.
The pair constructor takes two types as template parameters, used to specify the types
for the first and second values. Those elements are accessed directly using the first
and second data. Equivalently, these members can be accessed with the get<0>() and
get<1>() functions.
The make_pair() convenience function is used to create a value pair without explicitly
specifying the types:
std::pair<std::string, int> nameAndAge = std::make_pair("John", 32);
std::cout << "Name: " << nameAndAge.first << ", age: " << nameAndAge.second
<< std::endl;
The second line is equivalent to the following one:
std::cout << "Name: " << std::get<0>(nameAndAge) << ", age: " <<
std::get<1>(nameAndAge) << std::endl;

Pairs are used by unordered map, unordered multimap, map, and multimap containers
to manage their key/value elements.
Tuples are similar to pairs. The constructor allows you to provide a variable number
of template arguments. Elements are accessed with the get<N>() function only, which
returns the nth element inside the tuple, and there is a convenience function to create
them similar to that for pair, named make_tuple().
Additionally, tuples have another convenience function that's used to extract values
from them. The tie() function allows for the creation of a tuple of references, which is
useful in assigning selected elements from a tuple to specific variables.
std::optional | 207

Let's understand how to use the make_tuple() and get() functions to retrieve data from
a tuple:
#include <iostream>
#include <tuple>
#include <string>

int main()
{
std::tuple<std::string, int, float> james = std::make_tuple("James", 7,
1.90f);
std::cout << "Name: " << std::get<0>(james) << ". Agent number: " <<
std::get<1>(james) << ". Height: " << std::get<2>(james) << std::endl;
}

Output:
Name: James. Agent number: 7. Height: 1.9

std::optional
optional<T> is a that's used to contain a value that might be present or not.
The class takes a template parameter, T, which represents the type that the
std::optional template class might contain. Value type means that the instance of the
class contains the value. Copying optional will create a new copy of the contained data.
At any point in the execution of the program, optional<T> either contains nothing, when
it's empty, or contains a value of type T.
Optional is defined in the <optional> header.
Let's imagine our application is using a class named User for managing registered users.
We would like to have a function that gets us the information of a user from their email:
User getUserByEmail(Email email);.
But what happens when a user is not registered? That is, when we can determine that
our system does not have the associated User instance?
Some would suggest throwing an exception. In C++, exceptions are used for exceptional
situations, ones that should almost never happen. A user not being registered on our
website is a perfectly normal situation.
208 | Standard Library Containers and Algorithms

In these situations, we can use the optional template class to represent the fact that we
might not have the data:
std::optional<User> tryGetUserByEmail(Email email);

The optional template provides two easy methods to work with:


• has_value(): This returns true if optional is currently holding a value, and false if
the variant is empty.
• value(): This function returns the value currently held by optional, or throws an
exception if it's not present.
• Additionally, optional can be used as a condition in an if statement: it will evaluate
to true if it contains a value, or false otherwise.

Let's look at the following example to understand how the has_value() and value()
functions work:
#include <iostream>
#include <optional>

int main()
{
// We might not know the hour. But if we know it, it's an integer
std::optional<int> currentHour;
if (not currentHour.has_value()) {
std::cout << "We don't know the time" << std::endl;
}
currentHour = 18;
if (currentHour) {
std::cout << "Current hour is: " << currentHour.value() << std::endl;
}
}
Output:
We don't know the time
Current hour is: 18
std::optional | 209

The optional template comes with additional convenience features. We can assign the
std::nullopt value to optional to make it explicit when we want it empty, and we can
use the make_optional value to create an optional from a value. Additionally, we can
use the dereference operator, *, to access the value of optional without throwing an
exception if the value is not present. In such cases, we will access invalid data, so we
need to be sure that optional contains a value when we use *:
std::optional<std::string> maybeUser = std::nullopt;
if (not maybeUser) {
std::cout << "The user is not present" << std::endl;
}
maybeUser = std::make_optional<std::string>("email@example.com");
if (maybeUser) {
std::cout << "The user is: " << *maybeUser << std::endl;
}
Another handy method is value_or(defaultValue). This function takes a default value
and returns the value contained by optional if it currently holds a value, otherwise it
returns the default value. Let's explore the following example:
#include <iostream>
#include <optional>

int main()
{
std::optional<int> x;
std::cout << x.value_or(10) << std::endl;
//Will return value of x as 10
x = 15;
std::cout << x.value_or(10)<< std::endl;
//Will return value of x as 15
}
Output:
10
15
210 | Standard Library Containers and Algorithms

In addition to return values, optional is useful when accepting it as an argument to


represent arguments that can be present or not.
Let's recall our User class that's composed of an email address, a phone number, and
a physical address. Sometimes, users don't have a phone number and don't want to
provide a physical address, so the only required field we have in User is the email
address:
User::User(Email email, std::optional<PhoneNumber> phoneNumber =
std::nullopt, std::optional<Address> address = std::nullopt){
...
}
This constructor allows us to pass in all the information we have on the user. If, instead
of using optional, we used multiple overloads, we would have had four overloads:
1. Only email
2. Email and phone number
3. Email and address
4. Email with phone number and address

You can see that the number of overloads grows quickly when there are more
arguments that we might not want to pass.

std::variant
variant is a value type that's used to represent a choice of types. The class takes a list of
types, and the variant will be able to contain one value of any of those types.
It is often referred to as tagged union, because similar to a union, it can store multiple
types, with only one present at a time. It also keeps track of which type is currently
stored.
During the execution of a program, variant will contain exactly one of the possible
types at a time.
Like optional, variant is a value type: when we create a copy of variant, the element
that is currently stored is copied into the new variant.
std::variant | 211

To interact with std::variant, the C++ standard library gives us two main functions:
• holds_alternative<Type>(variant): It returns true if the variant is currently
holding the provided type, if not then false.
• get(variant): There are two versions: get<Type>(variant) and
get<Index>(variant).

get<Type>(variant) gets the value of the type that's currently stored inside
the variant. Before calling this function, the caller needs to be sure that holds_
alternative<Type>(variant) returns true.
get<Index>(variant) gets the value of the index type that's currently stored inside
variant. Like before, the caller needs to be sure that variant is holding the correct type.
For example, with std::variant<string, float> variant, calling get<0>(variant) will
give us the string value, but we need to be sure that variant is currently storing a string
at the moment. Usually, it is preferable to access the elements with get<Type>() so
that we are explicit on the type that we expect and that if the order of the types in the
variant changes, we will still get the same result:

Exercise 13: Using Variant in the Program


Let's perform the following steps to understand how to use variant in the program:
1. Include the required header files:
#include <iostream>
#include <variant>
2. In the main function, add the variant with the value type as string and integer:
int main()
{
std::variant<std::string, int> variant = 42;
3. Now using the two print statements call the variant in different ways:
std::cout << get<1>(variant) << std::endl;
std::cout << get<int>(variant) << std::endl;
The output is as follows:
Output:
42
42
212 | Standard Library Containers and Algorithms

An alternative way to get the content of variant is to use std::visit(visitor, variant),


which takes variant and a callable object. The callable objects need to support an
overload of operator(), taking a type for each of the possible types stored inside
variant. Then, visit will make sure to call the function that accepts the current type
that's stored inside variant:

Exercise 14: Visitor Variant


Let's perform the following steps to understand how to use std::visit(visitor, variant) in
the program:
1. Add the following header files at the start of the program:
#include <iostream>
#include <string>
#include <variant>
2. Now, add the struct Visitor as illustrated:
struct Visitor {
void operator()(const std::string& value){
std::cout << "a string: " << value << std::endl;
}
void operator()(const int& value){
std::cout << "an int: " << value << std::endl;
}
};
3. Now, in the main function, call the struct Visitor and pass values as illustrated:
int main()
{
std::variant<std::string, int> variant = 42;
Visitor visitor;
std::cout << "The variant contains ";
std::visit(visitor, variant);
variant = std::string("Hello world");
std::cout << "The variant contains ";
std::visit(visitor, variant);
}
std::variant | 213

The output is as follows:


The variant contains an int: 42
The variant contains a string: Hello world
variant is incredibly valuable when we want to represent a set of values of different
types. Typical examples are as follows:
• A function returning different types depending on the current state of the
program
• A class that represents several states

Let's imagine our std::optional<User> tryGetUserByEmail() function, which we


described earlier.
Thanks to optional, we could now write the function in a clear way, showing that
sometimes we would not retrieve the user. It is likely that if the user is not registered,
we might ask them whether they want to register.
Let's imagine we have struct UserRegistrationForm, which contains the information
that's needed to let the user register.
Our function can now return std::variant<User, UserRegistrationForm>
tryGetUserByEmail(). When the user is registered, we return User, but if the user is not
registered, we can return the registration form.
Additionally, what should we do when there is an error? With variant, we could have
struct GetUserError storing all the information we have so that our application will
be able to recover from the error and add it to the return type: std::variant<User,
UserRegistrationForm, GetUserError>, or tryGetUserByEmail().
Now we can have the complete picture of what is going to happen when we call
getUserByEmail() by just looking at the function signature, and the compiler will help us
make sure that we handle all the cases.
Alternatively, variant can also be used to represent the various states in which a class
can be. Each state contains the data that's required for that state, and the class only
manages the transitions from one state to another.
214 | Standard Library Containers and Algorithms

Activity 22: Airport System Management


Let's write a program to create airport system management:
1. We want to represent the state of an airplane in an airport system. The airplane
can be in three states: at_gate, taxi, or flying. The three states store different
information.
2. With at_gate, the airplane stores the gate number at which it is. With taxi, we
store which lane the airplane is assigned and how many passengers are on board.
With flying, we store the speed:
struct AtGate {
int gate;
};

struct Taxi {
int lane;
int numPassengers;
};

struct Flying {
float speed;
};
3. The airplane should have three methods:
• startTaxi(): This method takes the lane the airplane should go on and the
number of passengers on board. The airplane can start taxi only if it is at the
gate.
• takeOff(): This method takes the speed at which the airplane should fly. The
airplane can start flying only if it is in the taxi state.
• currentStatus(): This method prints the current status of the airplane.

Note
The solution for this activity can be found on page 306.
Iterators | 215

Iterators
In this chapter, we've mentioned multiple times that elements have a position in a
container: for example, we said that we can insert an element in a specific position in a
list.
Iterators are the way in which the position of an element in a container is represented.
They provide a consistent way to operate on elements of the container, abstracting the
details of the container to which the elements belong.
An iterator always belongs to a range. The iterator representing the start of the range,
can be accessed by the begin() function, while the iterator representing the end of the
range, non-inclusive, can be obtained with the end() function. The range where the first
element is included, but where the last one is excluded, is referred to as half-open.
The interface that the iterator must offer is composed of four functions:
1. The * operator provides access to the element at the position currently referenced
by the iterator.
2. The ++ operator is used to move forward to the next element.
3. Then, the == operator is used to compare two iterators to check whether they are
pointing to the same position.
Note that two iterators can only be compared if they are part of the same range:
they must represent the position of elements of the same container.
4. Finally, the = operator is used to assign an iterator.

Every container class in C++ must specify the type of iterator that it provides to access
its elements as a member type alias named iterator. For example, for a vector of
integer, the type would be std::vector<int>::iterator.
Let's see how we could use iterators to iterate over all the elements of a container (a
vector, in this case):
#include <iostream>
#include <vector>

int main()
{
216 | Standard Library Containers and Algorithms

std::vector<int> numbers = {1, 2, 3};


for(std::vector<int>::iterator it = numbers.begin(); it != numbers.
end(); ++it) {
std::cout << "The number is: " << *it << std::endl;
}
}
This looks complex for such an operation, and we saw in Chapter 1, Getting Started how
we can use range-based for:
for(int number: numbers) {
std::cout << "The number is: " << number << std::endl;
}
Range-based for works thanks to iterators: the compiler rewrites our range-based for to
look like the one we wrote with iterators. This allows the range-based for to work with
any type that provides begin() and end() functions and returns iterators.
The way operators provided by the iterators are implemented depends on the container
on which the iterator operates.
Iterator can be grouped into four categories. Each category builds on the previous
category, thus offering additional functionality:

Figure 5.17: Table presenting iterators and their descriptions


Iterators | 217

The following diagram gives more detail about C++ iterators:

Figure 5.18: Representation of iterators hierarchy in C++

Let's understand each of these iterators in more detail:


• Input iterator can step forward and allow you to read the element it is pointing to.
The iterator can be copied, but when one copy is incremented or dereferenced to
access the element, all the other copies are invalidated and cannot be incremented
or dereferenced anymore. Conceptually, it means that elements in a sequence that
are accessed through an input iterator can only be read once at most.
Input iterators are usually used in a pattern where access to elements is always
followed by a position increment.
Additionally, input iterators provide the == and != operators to check whether the
iterator is equal to the end() value.
Typically, input iterators are used to access elements from a stream of elements,
where the whole sequence is not stored in memory, but we are obtaining one
element at a time.
218 | Standard Library Containers and Algorithms

• Forward iterators are very similar to input iterators but provide additional
guarantees.
The same iterator can be dereferenced several times to access the element it
points to.
Additionally, when we increment or dereference a forward iterator, the other
copies are not invalidated: if we make a copy of a forward iterator, we can advance
the first one, and the second can still be used to access the previous element.
Two iterators that refer to the same element are guaranteed to be equal.
• Bidirectional iterators are also forward iterators with the additional ability to
iterate backward over the elements using the operator-- (position decrement)
member function.
• Random-access iterators are also bidirectional iterators with the additional ability
to directly access any position without the need of a linear scan, in constant time.
Random-access iterators are provided by the operator[] member function to
access elements at generic indexes and the binary operator+ and operator- to step
forward and backward of any quantity.

Exercise 15: Exploring Iterator


Perform the following steps to explore the four categories discussed in the previous
section and writing to the element it points to, it is also an Output Iterator:
1. Add the following header files at the start of the program:
#include <iostream>
#include <vector>
2. In the main function declare the vector named number:
int main()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto it = numbers.begin();
3. Perform the various arithmetic operations as illustrated:
std::cout << *it << std::endl; // dereference: points to 1
it++; // increment: now it points to 2
std::cout << *it << std::endl;
// random access: access the 2th element after the current one
std::cout << it[2] << std::endl;
--it; // decrement: now it points to 1 again
Iterators | 219

std::cout << *it << std::endl;


it += 4; // advance the iterator by 4 positions: now it points to 5
std::cout << *it << std::endl;
it++; // advance past the last element;
std::cout << "'it' is after the past element: " << (it == numbers.
end()) << std::endl;
}
The output is as follows:
1
2
4
1
5
'it' is after the past element: 1

Many of the iterators we will talk about are defined in the <iterator> header.

Reverse Iterators
Sometimes, we need to iterate though a collection of elements in reverse order.
C++ provides an iterator that allows us to do this: the reverse iterator.
A reverse iterator wraps a bidirectional iterator and swaps the operation increment with
the operation of decrement, and vice versa.
Because of this, when we are iterating a reverse iterator in the forward direction, we are
visiting the elements in a range in backward order.
We can reverse the range of a container by calling the following methods on a
container:

Figure 5.19: Table presenting iterator functions and their descriptions

Code that works on normal iterators, it will also work with reverse iterators.
For example, we can see how similar the code is to iterate in reverse order.
220 | Standard Library Containers and Algorithms

Exercise 16: Exploring Functions of Reverse Iterator


Let's perform the following steps to understand how functions in reverse iterator
works:
1. Add the following header files at the start of the program:
#include <iostream>
#include <vector>
2. In the main function, add the vector named numbers as illustrated:
int main()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
3. Now iterate through the number vector as illustrated:
for(auto rit = numbers.rbegin(); rit != numbers.rend(); ++rit) {
std::cout << "The number is: " << *rit << std::endl;
}
}
The output is as follows:
The number is: 5
The number is: 4
The number is: 3
The number is: 2
The number is: 1

Insert Iterators
Insert iterators, also called inserters, are used to insert new values into a container
rather than overwrite them.
There exist three types of inserters, which differ on the position in the container at
which they insert the elements.
Iterators | 221

The following table summarizes the different categories:

Figure 5.20: Table presenting iterator functions and their descriptions

Some algorithms, which we are going to see later in this chapter, require an iterator for
storing data. Insert iterators are usually used with such algorithms.

Stream Iterators
Stream iterators allow us to use streams as a source to read elements from or as a
destination to write elements to:

Figure 5.21: Table presenting iterator functions and their descriptions

Because we don't have a container in this case, we cannot call the end() method to get
the end iterator. A default constructed stream iterator counts as the end of any stream
range.
Let's look at a program that reads space-separated integers from the standard input.

Exercise 17: Stream Iterator


Let's perform the following steps to understand how functions in reverse stream works:
1. Add the required header files as illustrated:
#include <iostream>
#include <iterator>
222 | Standard Library Containers and Algorithms

2. Now, in the main function, add the istream iterator as illustrated:


int main()
{
std::istream_iterator<int> it = std::istream_iterator<int>(std::cin);
std::istream_iterator<int> end;
for(; it != end; ++it) {
std::cout << "The number is: " << *it << std::endl;
}
}
The output is as follows (input: 10):
The number is: 10

Iterator Invalidation
As we said, iterators represent the position of elements in a container.
This means that they are tightly tied with the container, and changes to the container
might move the elements: this means that iterators pointing to such an element can no
longer be used – they are invalidated.
It is extremely important to always check the invalidation contract when using iterators
with containers, as it is not specified what happens when using an invalidated iterator.
More commonly, invalid data is accessed or the program crashes, leading to bugs that
are hard to find.
If we keep in mind how the containers are implemented, as we saw earlier in this
chapter, we can more easily remember when an iterator is invalidated.
For example, we said that when we insert an element in a vector, we might have to
get more memory to store the element, in which case all the previous elements are
moved to the newly obtained memory. This means that all the iterators pointing to the
elements are now pointing to the old location of the elements: they are invalidated.
On the other hand, we saw that when we insert an element into the list, we only have
to update the predecessor and successor nodes, but the elements are not moved. This
means that the iterators to the elements remain valid:
#include <iostream>
#include <vector>
#include <list>

int main()
Iterators | 223

{
std::vector<int> vector = {1};
auto first_in_vec = vector.begin();
std::cout << "Before vector insert: " << *first_in_vec << std::endl;
vector.push_back(2);
// first_number is invalidated! We can no longer use it!
std::list<int> list = {1};
auto first_in_list = list.begin();
list.push_back(2);
// first_in_list is not invalidated, we can use it.
std::cout << "After list insert: " << *first_in_list << std::endl;
}
Output:
Before vector insert: 1
After list insert: 1

When there is a need to store iterators to elements, iterator invalidation is an important


consideration to make when deciding which container to use.

Exercise 18: Printing All of the Customers' Balances


We want to print the balances for all of the customers of our application. The balances
are already stored inside a vector as integers.
We want to use iterators to traverse the vector of balances. Follow these steps to do so:
1. Initially, we include the header file for the vector class, and we declare a vector of
10 elements of type int:
#include <vector>
std::vector<int> balances = {10, 34, 64, 97, 56, 43, 50, 89, 32, 5};
2. The for loop has been modified to iterate using the vector's iterator, starting from
the position returned by begin() until it reaches the one returned by end():
for (auto pos = numbers.begin(); pos != numbers.end(); ++pos)
{
// to be filled
}
224 | Standard Library Containers and Algorithms

3. The element of the array is accessed using the dereference operator (*) on the
iterator:
for (auto pos = numbers.begin(); pos != numbers.end(); ++pos)
{
std::cout << "Balance: " << *pos << std::endl;
}

Algorithms Provided by the C++ Standard Template Library


Algorithms are a way to operate on containers in an abstract way.
The C++ standard library provides a wide range of algorithms for all the common
operations that can be performed on ranges of elements.
Because algorithms accept iterators, they can operate on any container, even user-
defined containers, as long as they provide iterators.
This allows us to have a large number of algorithms that work with a large number
of containers, without the need for the algorithm to know how the container is
implemented.
The following are some of the most important and common algorithms that are
provided by the STL.

Note
Algorithms operate on ranges, so they normally take a pair of iterators: first and
last.

As we said earlier in this chapter, the last iterator denotes the element past the end
of the range – it is not part of the range.

This means that when we want to operate on a full container, we can pass begin()
and end() as arguments to the algorithm, but if we want to operate on a shorter
sequence, we must be sure that our last iterator is past the last item we want to
include in the range.
Algorithms Provided by the C++ Standard Template Library | 225

Lambda
Most of the algorithms accept a unary or binary predicate: a Functor (function object),
which accepts either one or two parameters. These predicates allow the user to
specify some of the actions that the algorithm requires. What the actions are vary from
algorithm to algorithm.
As we saw at the end of Chapter 3, Classes, to write a function object, we have to create
a class and overload the operator().
This can be very verbose, especially when the functor should perform a simple
operation.
To overcome this with C++, the user has to write a lambda expression, also called just a
lambda.
A lambda expression creates a special function object, with a type known only by the
compiler, that behaves like a function but can access the variables in the scope in which
it is created.
It is defined with a syntax very similar to the one of functions:
[captured variables] (arguments) { body }

This creates a new object that, when called with the arguments specified in the lambda
expression, executes the body of the function.
Arguments is the list of arguments the function accepts, and body is the sequence of
statements to execute when the function is invoked. They have the same meaning that
they have for functions, and the same rules we saw in Chapter 2, Functions, apply.
For example, let's create a lambda that takes two integers and returns their sum:
#include <iostream>

int main()
{
auto sum_numbers = [] (int a, int b) { return a + b; };
std::cout << sum_numbers(10, 20) << std::endl;
}
Output:
30

By default, the body of the lambda can only reference the variables that are defined in
the argument list and inside the body, like for functions.
226 | Standard Library Containers and Algorithms

Additionally, lambdas can capture a variable in the local scope, and use it in their body.
Captured variables entail a list of variable names that can be referenced in the body of
the lambda.
When a variable is captured, it is stored inside the created function object, and it can be
referenced in the body.
By default, the variables are captured by value, so they are copied inside the function
object:
#include <iostream>

int main()
{
int addend = 1;
auto sum_numbers = [addend](int b) { return addend + b; };
addend = 2;
std::cout << sum_numbers(3) << std::endl;
}
Output:
4
When we created the lambda, we captured addend by value: it was copied into the
sum_numbers object. Even if we modified the value of addend, we did not change the copy
stored inside sum_numbers, so when sum_numbers is executed, it sums 1 to b.
In some situations, we want to be able to modify the value of a variable in the scope in
which the lambda is created, or we want to access the actual value, not the value that
the variable had when the lambda was created.
In that case, we can capture by reference by prepending & to the variable name.

Note
When we capture by reference, we need to make sure that the variable that's been
captured by reference is still valid when the lambda is invoked, otherwise the body
of the function accesses an invalid object, resulting in bugs.
Prefer to capture by value when it is possible.
Algorithms Provided by the C++ Standard Template Library | 227

Let's look at an example:


#include <iostream>

int main()
{
int multiplier = 1;
auto multiply_numbers = [&multiplier](int b) { return multiplier * b; };
multiplier = 2;
std::cout << multiply_numbers(3) << std::endl;
}
Output:
6
Here, we capture the multiplier variable by reference: only a reference to it was stored
into multiply_numbers.
When we invoke multiply_numbers, the body accesses the current value of multiplier,
and since multiplier was changed to 2, that is the value that's used by the lambda.
A lambda can capture multiple variables, and each one can be either captured by value
or by reference, independently one from the other.

Read-Only Algorithms
Read-only algorithms are algorithms that inspect the elements stored inside a container
but do not modify the order of the elements of the container.
228 | Standard Library Containers and Algorithms

The following are the most common operations that inspect the elements of a range:

Figure 5.22: Table presenting the operations that inspect elements of a range

Let's see how we can use these functions:


#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
std::vector<int> vector = {1, 2, 3, 4};
bool allLessThen10 = std::all_of(vector.begin(), vector.end(), [](int
value) { return value < 10; });
std::cout << "All are less than 10: " << allLessThen10 << std::endl;
bool someAreEven = std::any_of(vector.begin(), vector.end(), [](int
value) { return value % 2 == 0; });
std::cout << "Some are even: " << someAreEven << std::endl;
bool noneIsNegative = std::none_of(vector.begin(), vector.end(), [](int
value) { return value < 0; });
std::cout << "None is negative: " << noneIsNegative << std::endl;
Algorithms Provided by the C++ Standard Template Library | 229

std::cout << "Odd numbers: " << std::count_if(vector.begin(), vector.


end(), [](int value) { return value % 2 == 1; }) << std::endl;

auto position = std::find(vector.begin(), vector.end(), 6);


std::cout << "6 was found: " << (position != vector.end()) << std::endl;
}
Output:
All are less than 10: 1
Some are even: 1
None is negative: 1
Odd numbers: 2
6 was found: 0

Modifying Algorithms
Modifying algorithms are algorithms that modify the collections they iterate on:

Figure 5.23: Table presenting the modifying algorithms


230 | Standard Library Containers and Algorithms

Let's see these algorithms in action:


#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main()
{
std::vector<std::string> vector = {"Hello", "C++", "Morning",
"Learning"};
std::vector<std::string> longWords;

std::copy_if(vector.begin(), vector.end(), std::back_inserter(longWords),


[](const std::string& s) { return s.length() > 3; });
std::cout << "Number of longWords: " << longWords.size() << std::endl;

std::vector<int> lengths;
std::transform(longWords.begin(), longWords.end(), std::back_
inserter(lengths), [](const std::string& s) { return s.length(); });

std::cout << "Lengths: ";


std::for_each(lengths.begin(), lengths.end(), [](int length) { std::cout
<< length << " "; });
std::cout << std::endl;

auto newLast = std::remove_if(lengths.begin(), lengths.end(), [](int


length) { return length < 7; });
std::cout << "No element removed yet: " << lengths.size() << std::endl;

// erase all the elements between the two iterators


lengths.erase(newLast, lengths.end());
std::cout << "Elements are removed now. Content: ";
std::for_each(lengths.begin(), lengths.end(), [](int length) { std::cout
Algorithms Provided by the C++ Standard Template Library | 231

<< length << " "; });


std::cout << std::endl;
}
Output:
Number of longWords: 3
Lengths: 5 7 8
No element removed yet: 3
Elements are removed now. Content: 7 8

Mutating Algorithms
Mutating algorithms are algorithms that change the order of elements:

Figure 5.24: Table presenting mutating algorithms

Let's see how we can use them:


#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <iterator>

int main()
{
std::vector<int> vector = {1, 2, 3, 4, 5, 6};

std::random_device randomDevice;
std::mt19937 randomNumberGenerator(randomDevice());
std::shuffle(vector.begin(), vector.end(), randomNumberGenerator);
std::cout << "Values: ";
232 | Standard Library Containers and Algorithms

std::for_each(vector.begin(), vector.end(), [](int value) { std::cout <<


value << " "; });
std::cout << std::endl;
}
Output:
Values: 5 2 6 4 3 1

Sorting Algorithms
This class of algorithms rearranges the order of elements within a container in a
specific order:

Figure 5.25: Table presenting sorting algorithms

Here is how to sort a vector:


#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
std::vector<int> vector = {5, 2, 6, 4, 3, 1};

std::sort(vector.begin(), vector.end());
std::cout << "Values: ";
std::for_each(vector.begin(), vector.end(), [](int value) { std::cout <<
value << " "; });
std::cout << std::endl;
}
Output:
Values: 1 2 3 4 5 6
Algorithms Provided by the C++ Standard Template Library | 233

Binary Search Algorithms


The following table explains the use of binary_search:

Figure 5.26: Table presenting the use of binary_search

Here's how you can utilize the binary search algorithm:


#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
std::vector<int> vector = {1, 2, 3, 4, 5, 6};

bool found = std::binary_search(vector.begin(), vector.end(), 2);


std::cout << "Found: " << found << std::endl;
}
Output:
Found: 1

Numeric Algorithms
This class of algorithms combines numeric elements using a linear operation in
different ways:

Figure 5.27: Table presenting the numeric algorithm


234 | Standard Library Containers and Algorithms

Let's see how we can use accumulate in the following program:


#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
std::vector<int> costs = {1, 2, 3};

int budget = 10;


int margin = std::accumulate(costs.begin(), costs.end(), budget, [](int
a, int b) { return a - b; });
std::cout << "Margin: " << margin << std::endl;
}
Output:
Margin: 4

Exercise 19: Customer Analytics


We have the information of many customers of our application and we want to compute
analytics data on that.
Given a map that has a username as a key and a user account as a value, we would like
to print the balances of the new users in descending order.
A user is considered new if they registered no more than 15 days ago. The struct
representing the user's account is provided and is as follows:
struct UserAccount {
int balance;
int daysSinceRegistered;
};
Algorithms Provided by the C++ Standard Template Library | 235

Write the void computeAnalytics(std::map<std::string, UserAccount>& accounts)


function, which prints the desired balances.
1. Make sure to include all the required headers for the solution:
#include <iostream>
#include <vector>
#include <iterator>
#include <map>
#include <algorithm>
2. First, we need to extract UserAccount from the map. Remember that the element
the map stores is pair containing a key and value. Since we need to transform the
type into UserAccount, we can use std::transform, by passing a lambda that only
returns the user account from the pair. To insert this into vector, we can use
std::back_inserter. Make sure to use a const reference when accepting pair in
the lambda that's passed to transform:
void computeAnalytics(std::map<std::string, UserAccount>& accounts) {
// Balance of accounts newer than 15 days, in descending order
std::vector<UserAccount> newAccounts;
std::transform(accounts.begin(), accounts.end(), std::back_
inserter(newAccounts),
[](const std::pair<std::string, UserAccount>& user) {
return user.second; });
}
3. After we have extracted the accounts in vector, we can use remove_if to remove all
accounts that are older than 15 days:
auto newEnd = std::remove_if(newAccounts.begin(), newAccounts.end(),
[](const UserAccount& account) { return account.daysSinceRegistered > 15;
} );
newAccounts.erase(newEnd, newAccounts.end());
236 | Standard Library Containers and Algorithms

4. After removing the old accounts, we need to sort the balances in descending
order. By default, std::sort uses an ascending order, so we need to provide a
lambda to change the order:
std::sort(newAccounts.begin(), newAccounts.end(), [](const
UserAccount& lhs, const UserAccount& rhs) { return lhs.balance > rhs.
balance; } );
Now that the data is sorted, we can print it:
for(const UserAccount& account : newAccounts) {
std::cout << account.balance << std::endl;
}
}
5. We can now invoke our function with the following test data:
int main()
{
std::map<std::string, UserAccount> users = {
{"Alice", UserAccount{500, 15}},
{"Bob", UserAccount{1000, 50}},
{"Charlie", UserAccount{600, 17}},
{"Donald", UserAccount{1500, 4}}
};
computeAnalytics(users);
}

Summary
In this chapter, we introduced sequential containers – containers whose elements can
be accessed in sequence. We looked at the array, vector, deque, list, and forward_list
sequential containers.
We saw what functionality they offer and how we can operate on them, and we saw how
they are implemented and how storage works for vector and list.
We followed this up with associative containers, containers that allow the fast lookup
of their elements, always kept in order. Set, multiset, map, and multimap are part of this
category.
We looked at the operations they support and how map and multimap are used to
associate a value to a key. We also saw their unordered version, which does not keep
elements in order but provides higher performance. Unordered_set and unordered_map
are in this category.
Summary | 237

Finally, we looked at unconventional containers. String is used to manipulate sequences


of characters, pair and tuple are used to hold various elements of different types,
optional is used to add optionality to a type, and variant is used to store a value that
could be of several types.
We then explored iterators and learned how they are used to abstract the concept of
containers and provide a common set of functionalities.
We looked at the various types of iterators, and we learned what iterator invalidation is
and why it is important to be aware of it.
We finally moved on to algorithms in the C++ standard, after explaining that lambda is
a convenient way of defining a function that can also access variables in the scope in
which it is created.
We divided the most common algorithms into various categories, and we looked at the
most important algorithms in those categories, including find, remove, and sort.
In the next chapter, you will learn how to use the advanced features of C++ to create
dynamic programs. 
Object-Oriented
6
Programming

Lesson Objectives

By the end of this chapter, you will be able to:

• Compose classes that inherit properties from other classes

• Implement polymorphism in C++ programs

• Implement interfaces

• Use best practices to manage dynamic memory

In this chapter, you will learn how to use the advanced features of C++ to create dynamic
programs.
240 | Object-Oriented Programming

Introduction
In earlier chapters, we learned about templates that are used to create functions and
classes that work with arbitrary types. This avoids duplication of work. However, using
templates is not applicable in all cases, or may not be the best approach. The limitation
of templates is that their types need to be known when the code is compiled.
In real-world cases, this is not always possible. A typical example would be a program
that determines what logging infrastructure to use depending on the value of a
configuration file.
Consider the following problems:
• While developing the application and executing tests, the application would use a
logger that prints detailed information.
• On the other hand, when the application is deployed to the PCs of its users, the
application would use a logger that prints error summaries and notifies the
developers if there are any errors.

We can solve these problems using the concept of inheritance in C++.

Inheritance
Inheritance allows the combination of one or more classes. Let's look at an example of
inheritance:
class Vehicle {
public:
TankLevel getTankLevel() const;
void turnOn();
};

class Car : public Vehicle {


public:
bool isTrunkOpen();
};

In this example, the Car class inherits from the Vehicle class, or, we can say Car derives
from Vehicle. In C++ terminology, Vehicle is the base class, and Car is the derived class.
Inheritance | 241

When defining a class, we can specify the classes it derives from by appending :,
followed by one or more classes, separated by a comma:
class Car : public Vehicle, public Transport {
}
When specifying the list of classes to derive from, we can also specify the visibility of
the inheritance – private, protected, or public.
The visibility modifier specifies who can know about the inheritance relationship
between the classes.
The methods of the base class can be accessed as methods of the derived class based on
the following rules:
Car car;
car.turnOn();
When the inheritance is public, the code external to the class knows that Car derives
from Vehicle. All the public methods of the base class are accessible as public method of
the derived class by the code in the program. The protected methods of the base class
can be accessed as protected by the methods of the derived class. When inheritance
is protected, all the public and protected members are accessible as protected by
the derived class. Only the derived class and classes that derive from it know about
inheritance; external code sees the two classes as unrelated.
Finally, when deriving with a private modifier, all the public and protected methods and
fields of the base class are accessible by the derived class as private.
The private methods and fields of a class are never accessible outside of that class.
Accessing the fields of the base class follows the same rules.
Let's see a summary:

Figure 6.1: Base class methods and the access level they provide
242 | Object-Oriented Programming

Inheritance creates a hierarchy of derived and base classes.


The Orange class can be derived from a Citrus class, which is in turn derived from a
Fruit class. Here is how it can be written:
class Fruit {
};
class Citrus: public Fruit {
};
class Orange: public Citrus {
};

The class Citrus can access the public and protected methods of class Fruit, whereas
class Orange will be able to access both Citrus' and Fruit's public and protected
methods (Fruit's public methods are accessible through Citrus).

Exercise 20: Creating a Program to Illustrate Inheritance in C++


Let's perform the following exercise to create a derived class that inherits from multiple
base classes:
1. Add the header file at the start of the program:
#include <iostream>
2. Add the first base class, named Vehicle:
// first base class
class Vehicle {
public:
int getTankCapacity(){
const int tankLiters = 10;
std::cout << "The current tank capacity for your car is " <<
tankLiters << " Liters."<<std::endl;
return tankLiters;
}
};
Inheritance | 243

3. Now add the second base class, named CollectorItem:


// second base class
class CollectorItem {
public:
float getValue() {
return 100;
}
};
4. Add the derived class, named Ferrari250GT, as illustrated here:
// Subclass derived from two base classes
class Ferrari250GT: protected Vehicle, public CollectorItem {
public:
Ferrari250GT() {
std::cout << "Thank you for buying the Ferrari 250 GT with tank
capacity " << getTankCapacity() << std::endl;
return 0;
}
};
5. Now, in the main function, instantiate the Ferrari250GT class and call the
getValue() method:
int main()
{
Ferrari250GT ferrari;
std::cout << "The value of the Ferrari is " << ferrari.getValue() <<
std::endl;

/* Cannot call ferrari.getTankCapacity() because Ferrari250GT inherits


from Vehicle with the protected specifier */
return 0;
}
The output will be as follows:
Output:
The current tank capacity for your car is 10 Liters.
Thank you for buying the Ferrari 250 GT with tank capacity 10
The value of the Ferrari is 100
244 | Object-Oriented Programming

The specifier is not mandatory. If it is omitted, it defaults to public for structs and to
private for classes.

Note
If you use inheritance to group together some functionality when implementing a
class, it is often correct to use private inheritance, as that is a detail of how you
are implementing the class, and it is not part the public interface of the class.
If, instead, you want to write a derived class that can be used in place of the base
class, use public inheritance.

When inheriting from a class, the base class gets embedded into the derived class. This
means that all the data of the base class also becomes part of the derived class in its
memory representation:

Figure 6.2: Representation of the derived class and the base class

A question might come up at this point – we are embedding the base class inside the
derived class. This means that we need to initialize the base class when we initialize
the derived class, otherwise, part of the class would be left uninitialized. When do we
initialize the base class?
When writing the constructor of the derived class, the compiler will implicitly call the
default constructor of the base class before any initialization takes place.
If the base class does not have a default constructor but has a constructor that accepts
parameters, then the derived class constructor can explicitly call it in the initialization
list. Otherwise, there will be an error.
Inheritance | 245

In a similar way to how the compiler calls the constructor of the base class when the
derived class is constructed, the compiler takes care of always calling the destructor of
the base class after the destructor of the derived class has run:
class A {
public:
A(const std::string& name);
};

class B: public A {
public:
B(int number) : A("A's name"), d_number(number) {}

private:
int d_number;
};

}
When B's constructor is called, the A needs to be initialized. Since A doesn't have
a default constructor, the compiler cannot initialize it for us: we have to call A's
constructor explicitly.
The copy constructor and the assignment operator generated by the compiler take
care of calling the constructor and operator of the base class.
When, instead, we write our implementation of the copy constructor and the
assignment operators, we need to take care of calling the copy constructor and
assignment operator.

Note
In many compilers, you can enable additional warnings that notify you if you forget
to add the calls to the base constructor.
246 | Object-Oriented Programming

It is important to understand that inheritance needs to model an is a relationship: when


you define a class, A to inherit from another class, B, you are saying that A is a B.
To understand this, a vehicle is a good example: a car is a vehicle, a bus is a vehicle, and
a truck is also a vehicle. A bad example would be for a car to inherit from an engine.
While the engine might have similar functionality to a car, such as a start method, it is
wrong to say that a car is an engine. The relationship, in this case, is has a: the car has
an engine; this relationship represents composition.

Note
Using an is a test to understand whether a relationship can use inheritance can
fail in some cases: for example, a square inheriting from a rectangle. When
the width of the rectangle is doubled, the area of the rectangle doubles, but the
area of the square quadruples. This means that code that expects to interact with
rectangles might get surprising results when using a square, even if the square,
mathematically, is a rectangle.

A more general rule is to use the Liskov Substitution Principle: if the A class inherits
from B, we could replace the A class anywhere the B class is used, and the code would
still behave correctly.
Up to now, we have seen examples of single inheritance: a derived class has a single
base class. C++ supports multiple inheritance: a class can derive from multiple classes.
Let's look at an example:
struct A {
};

struct B {
};

struct C : A, B {
};
In this example, the C struct derives both from A and from B.
Inheritance | 247

The rules on how inheritance works are the same for single and multiple inheritance:
the methods of all the derived classes are visible based on the visibility access specified,
and we need to make sure to call the appropriate constructors and assign an operator
for all of the base classes.

Note
It is usually best to have a shallow inheritance hierarchy: there should not be many
levels of derived classes.

When using a multi-level inheritance hierarchy or multiple inheritance, it's more likely
that you'll encounter some problems, such as ambiguous calls.
A call is ambiguous when the compiler cannot clearly understand which method to call.
Let's explore the following example:
struct A {
void foo() {}
};

struct B {
void foo() {}
};

struct C: A, B {
void bar() { foo(); }
};
In this example, it is not clear which foo() to call, A's or B's. We can disambiguate that by
prepending the name of the class followed by two columns: A::foo().
248 | Object-Oriented Programming

Exercise 21: Using Multiple Inheritance to Create a "Welcome to the


Community" Message Application
Let's use multiple inheritance to create an application to print a "welcome to the
community" message:
1. First, add the required header files in the program, as illustrated:
#include <iostream>
2. Now, add the required classes, DataScienceDev and FutureCppDev, with the required
print statement:
class DataScienceDev {
public:
DataScienceDev(){
std::cout << "Welcome to the Data Science Developer Community." <<
std::endl;
}
};

class FutureCppDev {
public:
FutureCppDev(){
std::cout << "Welcome to the C++ Developer Community." <<
std::endl;
}
};
3. Now, add the Student class as illustrated here:
class Student : public DataScienceDev, public FutureCppDev {
public:
Student(){
std::cout << "Student is a Data Developer and C++ Developer." <<
std::endl;
}
};
4. Now, invoke the Student class in the main function:
int main(){
Student S1;
return 0;
}
Inheritance | 249

The output will be as follows:


Welcome to the Data Science Developer Community.
Welcome to the C++ Developer Community.
Student is a Data Developer and C++ Developer.

Activity 23: Creating Game Characters


We want to write a new game, and in that game, create two types of characters – a hero
and enemies. Enemies can swing their swords, and the hero can cast a spell.
Here is how you can achieve the task:
1. Create a Character class that has a public method, moveTo, that prints Moved to
position.
2. Create a Position struct:
struct Position {
std::string positionIdentifier;
};
3. Create two classes, Hero and Enemy, that are derived from the Character class:
class Hero : public Character {
};

class Enemy : public Character {


};
4. Create a Spell class with the constructor that takes the name of the spell:
class Spell {
public:
Spell(std::string name) : d_name(name) {}

std::string name() const {


return d_name;
}
private:
std::string d_name;
}
250 | Object-Oriented Programming

5. The Hero class should have a public method to cast a spell. Use the value from the
Spell class.
6. The Enemy class should have a public method to swing a sword, which prints
Swinging sword.
7. Implement the main method, which calls these methods in various classes:
int main()
{
Position position{"Enemy castle"};
Hero hero;
Enemy enemy;
}
The output will be as follows:
Moved to position Enemy castle
Moved to position Enemy castle
Casting spell fireball
Swinging sword

Note
The solution for this activity can be found on page 309.

Polymorphism
In the previous section, we mentioned that inheritance is a solution that allows you to
change the behavior of code while a program is running. This is because inheritance
enables polymorphism in C++.
Polymorphism means many forms and represents the ability of objects to behave in
different ways.
We mentioned earlier that templates are a way to write code that works with many
different types at compilation time and, depending on the types used to instantiate the
template, the behavior will change.
Polymorphism | 251

This kind of pattern is called static polymorphism – static because it is known during
compilation time. C++ also supports dynamic polymorphism – having the behavior of
methods change while the program is running. This is powerful because we can react
to information we obtain only after we have compiled our program, such as user input,
values in configurations, or the kind of hardware the code is running on. This is possible
thanks to two features – dynamic binding and dynamic dispatch.

Dynamic Binding
Dynamic binding is the ability for a reference or a pointer of a base type to point to an
object of a derived type at runtime. Let's explore the following example:
struct A {
};

struct B: A{
};

struct C: A {
};

//We can write


B b;
C c;

A& ref1 = b;
A& ref2 = c;
A* ptr = nullptr;

if (runtime_condition()) {
ptr = &b;
} else {
252 | Object-Oriented Programming

ptr = &c;
}

Note
To allow dynamic binding, the code must know that the derived class derives from
the base class.

If the inheritance's visibility is private, then only code inside the derived class will
be able to bind the object to a pointer or reference of the base class.

If the inheritance is protected, then the derived class and every class deriving from
it will be able to perform dynamic binding. Finally, if the inheritance is public, the
dynamic binding will always be allowed.

This creates the distinction between the static type and the dynamic (or run-time) type.
The static type is the type we can see in the source code. In this case, we can see that
ref1 has a static type of a reference to the A struct.
The dynamic type is the real type of the object: the type that has been constructed in
the object's memory location at runtime. For example, the static type of both ref1 and
ref2 is a reference to the A struct, but the ref1 dynamic type is B, since ref1 refers to a
memory location in which an object of type B has been created, and the ref2 dynamic
type is C for the same reason.
As said, the dynamic type can change at runtime. While the static type of a variable is
always the same, its dynamic type can change: ptr has a static type, which is a pointer
to A, but its dynamic type could change during the execution of the program:
A* ptr = &b; // ptr dynamic type is B
ptr = &c; // ptr dynamic type is now C
It is important to understand that only references and pointers can be assigned values
from a derived class safely. If we were to assign an object to a value type, we would get a
surprising result – the object would get sliced.
We said earlier that a base class is embedded inside a derived class. Say, for example, we
were to try and assign to a value, like so:
B b;
A a = b;
Polymorphism | 253

The code would compile, but only the embedded part of A inside of B would be copied
– when we declare a variable of type A, the compiler dedicates an area of the memory
big enough to contain an object of type A, so there cannot be enough space for B. When
this happens, we say that we sliced the object, as we took only a part of the object when
assigning or copying.

Note
It is not the intended behavior to slice the object. Be mindful of this interaction and
try to avoid it.

This behavior happens because C++ uses static dispatch by default for function and
method calls: when the compiler sees a method call, it will check the static type
of the variable on which the method is called, and it will execute the respective
implementation. In case of slicing, the copy constructor or assignment operator of A is
called, and it only copies the part of A inside B, ignoring the remaining fields.
As said before, C++ supports dynamic dispatch. This is done by marking a method with a
special keyword: virtual.
If a method is marked with the virtual keyword, when the method is called on a
reference or a pointer, the compiler will execute the implementation of the dynamic
type instead of the static type.
These two features enable polymorphism – we can write a function that accepts a
reference to a base class, call methods on this base class, and the methods of the
derived classes will be executed:
void safeTurnOn(Vehicle& vehicle) {
if (vehicle.getFuelInTank() > 0.1 && vehicle.batteryHasEnergy()) {
vehicle.turnOn();
}
}
We can then call the function with many different types of vehicles, and the appropriate
methods will be executed:
Car myCar;
Truck truck;
safeTurnOn(myCar);
safeTurnOn(truck);
254 | Object-Oriented Programming

A typical pattern is to create an interface that only specifies the methods that are
required for some functionality.
Classes that need to be used with such functionality must derive the interface and
implement all the required methods.

Virtual Methods
We've learned the advantages of dynamic dispatch in C++ and how it can enable us to
execute the methods of a derived class by calling a method on a reference or pointer to
a base class.
In this section, we will take an in-depth look at how to tell the compiler to perform
dynamic dispatch on a method. The way to specify that we want to use dynamic
dispatch for a method is to use the virtual keyword.
The virtual keyword is used in front of a method when declaring it:
class Vehicle {
public:
virtual void turnOn();
};

We need to remember that the compiler decides how to perform method dispatch
based on the static type of the variable that is used when calling the method.
This means that we need to apply the virtual keyword to the type we are using in the
code. Let's examine the following exercise to explore the virtual keyword.

Exercise 22: Exploring the Virtual Method


Let's create a program using the concept of inheritance using the virtual keyword:
1. First, make sure to add the required header file and namespace to compile the
program.
2. Now, add the Vehicle class as illustrated:
class Vehicle {
public:
void turnOn() {
std::cout << "Vehicle: turn on" << std::endl;
}
};
Virtual Methods | 255

3. In the Car class, add the virtual keyword as illustrated :


class Car : public Vehicle {
public:
virtual void turnOn() {
std::cout << "Car: turn on" << std::endl;
}
};

void myTurnOn(Vehicle& vehicle) {


std::cout << "Calling turnOn() on the vehicle reference" << std::endl;
vehicle.turnOn();
}
4. Now, in the main function, invoke the Car class and pass the car object in the
myTurnOn() function:
int main() {
Car car;
myTurnOn(car);
}
The output will be as follows:
Calling turnOn() on the vehicle reference
Vehicle: turn on
Here, the call will not be dynamically dispatched, and the call to the implementation
of Vehicle::turnOn() will be executed. The reason is that the static type of the variable
is Vehicle, and we did not mark the method as virtual, so the compiler uses static
dispatch.
The fact that we wrote a Car class that declares the method virtual is not important,
since the compiler only sees the Vehicle class being used in myTurnOn(). When a method
is declared virtual, we can override it in a derived class.
To override a method, we need to declare it with the same signature as the parent class:
the same return type, name, parameters (including const-ness and ref-ness), const
qualifier, and the other attributes.
256 | Object-Oriented Programming

If the signature does not match, we will create an overload for the function. The
overload will be callable from the derived class, but it will never be executed with a
dynamic dispatch from a base class, for example:
struct Base {
virtual void foo(int) = 0;
};
struct Derived: Base {
/* This is an override: we are redefining a virtual method of the base
class, using the same signature. */
void foo(int) { }

/* This is an overload: we are defining a method with the same name of a


method of the base class, but the signature is different. The rules regarding
virtual do not apply between Base::foo(int) and Derived:foo(float). */
void foo(float) {}
};

When a class overrides a virtual method of the base class, the method of the most
derived class will be executed when the method is called on a base class. This is true
even if the method is called from inside the base class, for example:
struct A {
virtual void foo() {
std::cout << "A's foo" << std::endl;
}
};

struct B: A {
virtual void foo() override {
std::cout << "B's foo" << std::endl;
}
};

struct C: B {
virtual void foo() override {
Virtual Methods | 257

std::cout << "C's foo" << std::endl;


}
};

int main() {
B b;
C c;
A* a = &b;

a->foo(); // B::foo() is executed


a = &c;
a->foo();
/* C::foo() is executed, because it's the most derived Class overriding
foo(). */
}
We can see a new keyword in the preceding example: the override keyword.
C++11 introduced this keyword to enable us to specify that we are overriding a method
explicitly. This allows the compiler to give us an error message if we use the override
keyword, but the signature does not match any base class' virtual method.

Note
Always use the override keyword when you are overriding a method. It is easy
to change the signature of the base class and forget to update all the locations
where we overrode the method. If we do not update them, they will become a new
overload instead of an override!

In the example, we also used the virtual keyword for each function. This is not
necessary, since a virtual method on a base class makes every method with the same
signature in the derived classes virtual as well.
It is good to be explicit virtual keyword, but if we are already using the override
keyword, it might be redundant – in these cases, the best way is to follow the coding
standard of the project you are working on.
258 | Object-Oriented Programming

The virtual keyword can be applied to any method. Since the constructor is not a
method, the constructor cannot be marked as virtual. Additionally, dynamic dispatch is
disabled inside constructors and destructors.
The reason is that when constructing a hierarchy of derived classes, the constructor of
the base class is executed before the constructor of the derived class. This means that
if we were to call the virtual method on the derived class when constructing the base
class, the derived class would not be initialized yet.
Similarly, when calling the destructor, the destructors of the whole hierarchy are
executed in reverse order; first the derived and then the base class. Calling a virtual
method in the destructor would call the method on a derived class that has already
been destructed, which is an error.
While the constructor cannot be marked as virtual, the destructor can. If a class defines
a virtual method, then it should also declare a virtual destructor.
Declaring a destructor virtual is extremely important when classes are created on
dynamic memory, or the heap. We are going to see later in this chapter how to manage
dynamic memory with classes, but for now, it is important to know that if a destructor
is not declared virtual, then an object might be only partially destructed.

Note
If a method is marked virtual, then the destructor should also be marked virtual.

Activity 24: Calculating Employee Salaries


We are writing a system to compute the paycheques for the employees of a company.
Each employee has a base salary plus a bonus.
For employee who are not managers, the bonus is computed from the performance of
the department: they get 10% of the base salary if the department reached its goal.
The company also has managers, for whom the bonus is computed in a different way:
they get 20% of the base salary if the department reached its goal, plus 1% of the
difference between the achieved result of the department and the expected one.
We want to create a function that takes an employee and computes their total salary,
summing the base salary and the bonus, regardless of whether they are a manager or
not.
Virtual Methods | 259

Perform the following steps:


1. The Department class accepts the expected earning and the effective earning
when constructed, and stores them in two fields:
class Department {
public:

Department(int expectedEarning, int effectiveEarning)


: d_expectedEarning(expectedEarning), d_
effectiveEarning(effectiveEarning)
{}

bool hasReachedTarget() const {return d_effectiveEarning >= d_


expectedEarning;}
int expectedEarning() const {return d_expectedEarning;}
int effectiveEarning() const {return d_effectiveEarning;}
private:
int d_expectedEarning;
int d_effectiveEarning;
};
2. Define an Employee class with two virtual functions, getBaseSalary(), and
getBonus(). Within it, implement the logic for employee bonus calculation if the
department goal is met:
class Employee {
public:
virtual int getBaseSalary() const { return 100; }

virtual int getBonus(const Department& dep) const {


if (dep.hasReachedTarget()) {
return int(0.1 * getBaseSalary());
}
return 0;
}

};
260 | Object-Oriented Programming

3. Create another function that provides the total compensation:


int getTotalComp(const Department& dep) {
return getBaseSalary() + getBonus(dep);
}
4. Create a Manager class that derives from Employee. Again, create the same virtual
functions, getBaseSalary() and getBonus(). Within it, implement the logic for a
Manager bonus calculation if the department goal is met:
class Manager : public Employee {
public:
virtual int getBaseSalary() const override { return 150; }

virtual int getBonus(const Department& dep) const override {


if (dep.hasReachedTarget()) {
int additionalDeparmentEarnings = dep.effectiveEarning() -
dep.expectedEarning();
return int(0.2 * getBaseSalary() + 0.01 *
additionalDeparmentEarnings);
}
return 0;
}
};
5. Implement the main program, and run the program:
The output will be as follows:
Employee: 110. Manager: 181

Note
The solution for this activity can be found on page 311.

Interfaces in C++
In the previous section, we saw how to define a method that is virtual, and how the
compiler will do dynamic dispatch when calling it.
We have also talked about interfaces throughout the chapter, but we never specified
what an interface is.
Interfaces in C++ | 261

An interface is a way for the code to specify a contract that the caller needs to provide
to be able to call some functionality. We looked at an informal definition when talking
about the templates and the requirements they impose on the types used with them.
Functions and methods which accepts parameters as interface are a way of saying: in
order to perform my actions, I need these functionalities; it's up to you to provide them.
To specify an interface in C++, we can use an Abstract Base Class (ABC).
Let's dive into the name; the class is:
• Abstract: This means that it cannot be instantiated
• Base: This means it is designed to be derived from

Any class that defines a pure virtual method is abstract. A pure virtual method is a
virtual method that ends with = 0, for example:
class Vehicle {
public:
virtual void turnOn() = 0;
};

A pure virtual method is a method that does not have to be defined. Nowhere in the
previous code have we specified the implementation of Vehicle::turnOn(). Because of
this, the Vehicle class cannot be instantiated, as we do not have any code to call for its
pure virtual methods.
We can instead derive from the class and override the pure virtual method. If a class
derives from an abstract base class, it can be either of the following:
• Another abstract base class if it declares an additional pure virtual method, or if it
does not override all the pure virtual methods of the base class
• A regular class if it overrides all the pure virtual methods of the base class
262 | Object-Oriented Programming

Let's continue with the previous example:


class GasolineVehicle: public Vehicle {
public:
virtual void fillTank() = 0;
};
class Car : public GasolineVehicle {
virtual void turnOn() override {}
virtual void fillTank() override {}
};
In this example, Vehicle is an abstract base class and GasolineVehicle is too, since it
does not override all the pure virtual methods of Vehicle. It also defines an additional
virtual method, which the Car class overrides together with the Vehicle::turnOn()
method. This makes Car the only concrete class, a class that can be instantiated.
The same concept applies when a class is deriving from multiple abstract base classes:
all the pure virtual methods of all the classes that need to be overridden in order to
make the class concrete and thus instantiable.
While abstract base classes cannot be instantiated, we can define references and
pointers to them.

Note
If you try to instantiate an abstract base class, the compiler will give an error
specifying which methods are still pure virtual, thus making the class abstract.

Functions and methods that require specific methods can accept references and
pointers to abstract base classes, and instances of concrete classes that derive from
them can be bound to such references.

Note
It is good practice for the consumer of the interface to define the interface.

A function, method, or class that requires some functionality to perform its actions
should define the interface. Classes that should be used with such entities should
implement the interface.
Interfaces in C++ | 263

Since C++ does not provide a specialized keyword for defining interfaces and interfaces
are simply abstract base classes, there are some guidelines that it's best practice to
follow when designing an interface in C++:
• An abstract base class should not have any data members or fields.
The reason for this is that an interface specifies behavior, which should be
independent of the data representation. It derives that abstract base classes
should only have a default constructor.
• An abstract base class should always define a virtual destructor.
The definition of a destructor should be the default one: virtual ~Interface() =
default. We are going to see why it is important for the destructor to be virtual
later.
• All the methods of an abstract base class should be pure virtual.
The interface represents an expected functionality that needs to be implemented;
a method which is not pure is an implementation. The implementation should be
separate from the interface.
• All of the methods of an abstract base class should be public.
Similar to the previous point, we are defining a set of methods that we expect to
call. We should not limit which classes can call the method only to classes deriving
from the interface.
• All the methods of an abstract base class should be regarding a single functionality.
If our code requires multiple functionalities, separate interfaces can be created,
and the class can derive from all of them. This allows us to compose interfaces
more easily.

Consider disabling the copy and move constructors and assignment operators on the
interface. Allowing the interface to be copied can cause the slicing problem we were
describing before:
Car redCar;
Car blueCar;

Vehicle& redVehicle = redCar;


Vehicle& redVehicle = blueCar;
redVehicle = blueVehicle;
// Problem: object slicing!
264 | Object-Oriented Programming

With the last assignment, we only copied the Vehicle part, since the copy constructor
of the Vehicle class has been called. The copy constructor is not virtual, so the
implementation in Vehicle is called, and since it only knows about the data members
of the Vehicle class (which should be none), the ones defined inside Car have not been
copied! This results in problems that are very hard to identify.
A possible solution is to disable the interface copy and move construct and assign
operator: Interface(const Interface&) = delete; and similar. This has the drawback of
disabling the compiler from creating the copy constructor and assign operators of the
derived classes.
An alternative is to declare copy/move constructor/assignment protected so that only
derived classes can call them, and we don't risk assigning interfaces while using them.

Activity 25: Retrieving User Information


We are writing an application to allow users to buy and sell items. When a user logs in,
we need to retrieve several pieces of information to populate their profile, such as the
URL for the profile picture and the full name.
Our service is running in many data centers around the world, to always be close to its
customers. Because of that, sometimes we want to retrieve information for the user
from a cache, but sometimes we want to retrieve it from our main database.
Perform the following:
1. Let's write the code, which can be independent of where the data is coming from,
so we create an abstract UserProfileStorage class to retrieve the CustomerProfile
from UserId:
struct UserProfile {};
struct UserId {};

class UserProfileStorage {
public:
virtual UserProfile getUserProfile(const UserId& id) const = 0;

virtual ~UserProfileStorage() = default;

protected:
UserProfileStorage() = default;
UserProfileStorage(const UserProfileStorage&) = default;
UserProfileStorage& operator=(const UserProfileStorage&) = default;
};
Interfaces in C++ | 265

2. Now, write the UserProfileCache class, which inherits from UserProfileStorage:


class UserProfileCache : public UserProfileStorage {
public:
UserProfile getUserProfile(const UserId& id) const override {
std::cout << "Getting the user profile from the cache" <<
std::endl;
return UserProfile();
}
};
void exampleOfUsage(const UserProfileStorage& storage) {
UserId user;
std::cout << "About to retrieve the user profile from the storage" <<
std::endl;
UserProfile userProfile = storage.getUserProfile(user);
}
3. In the main function, instantiate the UserProfileCache class and the call
exampleOfUsage function as illustrated:
int main()
{
UserProfileCache cache;
exampleOfUsage (cache);
}
The output is as follows:
About to retrieve the user profile from the storage
Getting the user profile from the cache

Note
The solution for this activity can be found at page 312.
266 | Object-Oriented Programming

Dynamic Memory
In this chapter, we have come across the term dynamic memory. Now let's understand
in more detail what dynamic memory is, what problems it solves, and when to use it.
Dynamic memory is the part of the memory that the program can use to store objects,
for which the program is responsible for maintaining the correct lifetime.
It is usually also called the heap and is often the alternative to the stack, which instead
is handled automatically by the program. Dynamic memory can usually store much
larger objects than the stack, which usually has a limit.
A program can interact with the operating system to get pieces of dynamic memory
that it can use to store objects, and later it must take care to return such memory to the
operating system.
Historically, developers would make sure they called the appropriate functions to get
and return memory, but modern C++ automates most of this, so it is much easier to
write correct programs nowadays.
In this section, we are going to show how and when it is recommended to use dynamic
memory in a program.
Let's start with an example: we want to write a function that will create a logger. When
we execute tests, we create a logger specifically for the test called TestLogger, and when
we run our program for users, we want to use a different logger, called ReleaseLogger.
We can see a good fit for interfaces here – we can write a logger abstract base class
that defines all the methods needed for logging and have TestLogger and ReleaseLogger
derive from it.
All our code will then use a reference to the logger when logging.
How can we write such a function?
As we learned in Chapter 2, Functions, we cannot create the logger inside the function
and then return a reference to it, since it would be an automatic variable and it would
be destructed just after the return, leaving us with a dangling reference.
We cannot create the logger before calling the function and let the function initialize
it either, since the types are different, and the function knows which type should be
created.
We would need some storage that is valid until we need the logger, to put the logger in
it.
Dynamic Memory | 267

Given only an interface, we cannot know the size of the classes implementing it, since
multiple classes could implement it and they could have different sizes. This prevents
us from reserving some space in memory and passing a pointer to such space to the
function, so that it could store the logger in it.
Since classes can have different sizes, the storage not only needs to remain valid longer
than the function, but it also needs to be variable. That is dynamic memory!
In C++, there are two keywords to interact with dynamic memory – new and free.
The new expression is used to create a new object in dynamic memory – it is composed
by the new keyword, followed by the type of the object to create and the parameters to
pass to the constructor, and returns a pointer to the requested type:
Car* myCar = new myCar();

The new expression requests a piece of dynamic memory big enough to hold the object
created and instantiates an object in that memory. It then returns a pointer to such an
instance.
The program can now use the object pointed to by myCar until it decides to delete it.
To delete a pointer, we can use the delete expression: it is composed by the delete
keyword followed by a variable, which is a pointer:
delete myCar;

The delete keyword calls the destructor of the object pointed to by the pointer
provided to it, and then gives the memory we initially requested back to the operating
system.
Deleting pointers to automatic variables lead to an error as follows:
Car myCar; // automatic variable
delete &myCar; // This is an error and will likely crash the program
It is of absolute importance that, for each new expression, we call the delete expression
only once, with the same returned pointer.
If we forget to call the delete function on an object returned by calling the new function,
we will have two major problems:
• The memory will not be returned to the operating system when we do not need it
anymore. This is known as a memory leak. If this repeatedly happens during the
execution of the program, our program will take more and more memory, until it
consumes all the memory it can get.
• The destructor of the object will not be called.
268 | Object-Oriented Programming

We saw in previous chapters that, in C++, we should make use of RAII and get the
resources we need in the constructor and return them in the destructor.
If we do not call the destructor, we might not return some resources. For example, a
connection to the database would be kept open, and our database would struggle due
to too many connections being open, even if we are using only one.
The problem that arises if we call delete multiple times on the same pointer is that all
the calls after the first one will access memory they should not be accessing.
The result can range from our program crashing to deleting other resources our
program is currently using, resulting in incorrect behavior.
We can now see why it is extremely important to define a virtual destructor in the base
class if we derive from it: we need to make sure that the destructor of the runtime
type is called when calling the delete function on the base object. If we call delete on a
pointer to the base class while the runtime type is the derived class, we will only call the
destructor of the base class and not fully destruct the derived class.
Making the destructor of the base class virtual will ensure that we are going to call the
derived destructor, since we are using dynamic dispatch when calling it.

Note
For every call to the new operator, there must be exactly one call to delete with the
pointer returned by new!

This error is extremely common and leads to many errors.


Like single objects, we can also use dynamic memory to create arrays of objects. For
such use cases, we can use the new[] and delete[] expressions:
int n = 15;
Car* cars = new Car[n];
delete[] cars;
The new[] expression will create enough space for n Car instances and will initialize
them, returning a pointer to the first element created. Here, we are not providing the
arguments to the constructor, so the class must have a default constructor.
With new[], we can specify how many elements we want it to initialize. This is different
from std::array and the built-in array we saw earlier because n can be decided at
runtime.
Dynamic Memory | 269

We need to call delete[] on the pointer returned by new[] when we do not need the
objects anymore.

Note
For every call to new[], there must be exactly one call to delete[] with the pointer
returned by new[].

The new operator and new[] function calls, and delete and delete[] function
calls, cannot be intermixed. Always pair the ones for an array or the ones for single
elements!

Now that we have seen how to use dynamic memory, we can write the function to
create our logger.
The function will call the new expression in its body to create an instance of the correct
class, and it will then return a pointer to the base class so that the code calling it does
not need to know about the type of logger created:
Logger* createLogger() {
if (are_tests_running()) {
TestLogger* logger = new TestLogger();
return logger;
} else {
ReleaseLogger logger = new ReleaseLogger("Release logger");
return logger;
}
}

There are two things to note in this function:


• Even if we wrote the new expression twice, new will be called only once per
function call.
This shows us that it is not enough to make sure we type new and delete an equal
number of times; we need to understand how our code is executed.
• There is no call to delete! This means that the code calling the createLogger
function needs to make sure to call delete.
270 | Object-Oriented Programming

From these two points, we can can see why it is error prone to manage memory
manually, and why it should be avoided whenever possible.
Let's look at an example of how to call the function correctly:
Logger* logger = createLogger();
myOperation(logger, argument1, argument2);
delete logger;
If myOperation does not call delete on the logger, this is a correct use of dynamic
memory. Dynamic memory is a powerful tool, but doing it manually is risky, error prone,
and easy to get wrong.
Fortunately, modern C++ provides some facilities to make all this much easier to do. It is
possible to write entire programs without ever using new and delete directly.
We will see how in the next section.

Safe and Easy Dynamic Memory


In the previous section, we learned how dynamic memory could be useful when
working with interfaces, especially when creating new instances of derived classes.
We also saw how working with dynamic memory can be hard – we need to make sure
to call new and delete in pairs, and failing to do so always has negative effects on our
program. Fortunately for us, since C++11, there are tools in the standard library to help
us overcome such limitations – smart pointers.
Smart pointers are types that behave like pointers, which are called raw pointers in this
context, but have additional functionality.
We are going to look at two smart pointers from the standard library: std::unique_ptr
and std::shared_ptr (read as unique pointer and shared pointer). Both pointers
are used to free the developer from the complexity of making sure to call delete
appropriately.
They represent different ownership models. The owner of an object is the code that
determines the lifetime of the object – the part of the code that decides when to create
and when to destroy the object.
Safe and Easy Dynamic Memory | 271

Usually, ownership is associated with the scope a function or method, since the lifetime
of automatic variables is controlled by it:
void foo() {
int number;
do_action(number);
}

In this case, the scope of foo() owns the number object, and it will make sure it is
destroyed when the scope exits.
Alternatively, classes might own objects when they are declared as value types between
the data members of the class. In that case, the lifetime of the object will be the same as
the lifetime of the class:
class A {
int number;
};
number will be constructed when the A class is constructed and will be destroyed when
the A class is destroyed. This is automatically done because the field number is embedded
inside the class and the constructor and destructor of the class will automatically
initialize number.
When managing objects in dynamic memory, ownership is not enforced by the compiler
anymore, but it is helpful to apply the concept of ownership to the dynamic memory as
well – the owner is who decides when to delete the object.
A function could be the owner of an object when the object is allocated with the new
call inside the function, as in the following example:
void foo() {
int* number = new number();
do_action(number);
delete number;
}
272 | Object-Oriented Programming

Or a class might own it, by calling new in the constructor and storing the pointer in its
fields, and calling delete on it in the destructor:
class A {
A() : number(new int(0)) {
}
~A() {
delete number;
}
int* number;
};
But the ownership of dynamic objects can also be passed around.
We looked at an example earlier with the createLogger function. The function creates
an instance of Logger and then passes the ownership to the parent scope. Now, the
parent scope is in charge of making sure the object is valid until it is accessed in the
program and deleted afterward.
Smart pointers allow us to specify the ownership in the type of the pointer and make
sure it is respected so that we do not have to keep track of it manually anymore.

Note
Always use smart pointers to represent the ownership of objects.

In a code base, smart pointers should be the pointers that control the lifetime of
objects, and raw pointers, or regular pointers, are used only to reference objects.

A Single Owner Using std::unique_ptr


unique_ptr is the pointer type that's used by default. The unique pointer points to an
object that has a single owner; there is a single place in the program that decides when
to delete the object.
An example is the logger from before: there is a single place in the program that
determines when to delete the object. Since we want the logger to be available as long
as the program is running, to always be able to log information, we will destroy the
logger only at the end of the program.
Safe and Easy Dynamic Memory | 273

The unique pointer guarantees the uniqueness of ownership: the unique pointer cannot
be copied. This means that once we have created a unique pointer for an object, there
can be only one.
Additionally, when the unique pointer is destroyed, it deletes the object it owns. This
way, we have a concrete object that tells us the ownership of the created object, and we
do not have to manually make sure that only one place is calling delete for the object.
A unique pointer is a template that can take one argument: the type of the object.
We could rewrite the previous example as follows:
std::unique_ptr<Logger> logger = createLogger();

While this code would compile, we would not be respecting the guideline we mentioned
previously regarding always using smart pointers for ownership: createLogger returns a
raw pointer, but it passes ownership to the parent scope.
We can update the signature of createLogger to return a smart pointer:
std::unique_ptr<Logger>createLogger();

Now, the signature expresses our intention, and we can update the implementation to
make use of smart pointers.
As we mentioned earlier, with the use of smart pointers, code bases should not use
new and delete anywhere. This is possible because the standard library, since C++14,
offers a convenient function: std::make_unique. make_unique is a template function that
takes the type of the object to create, and creates it in dynamic memory, passing its
arguments to the object's constructor and returning a unique pointer to it:
std::unique_ptr<Logger>createLogger() {
if (are_tests_running()) {
std::unique_ptr<TestLogger> logger = std::make_unique<TestLogger>();
return logger; // logger is implicitly moved
} else {
std::unique_ptr<ReleaseLogger> logger = std::make_
unique<ReleaseLogger>("Release logger");
return logger; // logger is implicitly moved
}
}
274 | Object-Oriented Programming

There are three important points regarding this function:


• There is no longer a new expression in the body; it has been replaced with make_
unique. The make_unique function is simple to call because we can provide all
the arguments we would pass to the constructor of the type and have it created
automatically.
• We are creating a unique_ptr to a derived class, but we are returning a unique_ptr
to a base class.
Indeed, unique_ptr emulates the ability of raw pointers to convert pointers to
derived classes to pointers to base classes. This makes using unique_ptr as simple
as using raw pointers.
• We are using the move on the unique_ptr. As we said earlier, we cannot copy
unique_ptr, but we are returning from a function, so we must use a value;
otherwise, a reference would become invalid after the function returns, as we saw
in Chapter 2, Functions.
While it cannot be copied, unique_ptr can be moved. When we move unique_ptr,
we are transferring the ownership of the object to which it points to the recipient
of the value. In this case, we are returning value, so we are transferring the
ownership to the caller of the function.

Let's now see how we can rewrite the class that owns the number we showed before:
class A {
A(): number(std::make_unique<int>()) {}
std::unique_ptr<int> number;
};

Thanks to the fact that unique_ptr deletes the object automatically when it is destroyed,
we did not have to write the destructor for the class, making our code even easier.
If we need to pass a pointer to the object, without transferring ownership, we can use
the get() method on the raw pointer. Remember that raw pointers should not be used
for ownership, and code accepting the raw pointer should never call delete on it.
Thanks to these features, unique_ptr should be the default choice to keep track of the
ownership of an object.
Safe and Easy Dynamic Memory | 275

Shared Ownership Using std::shared_ptr


shared_ptr represents an object that has multiple owners: one out of several objects will
delete the owned object.
An example could make a TCP connection, which is established by multiple threads to
send data. Each thread uses the TCP connection to send data and then terminates.
We want to delete the TCP connection when the last thread has finished executing, but
it is not always the same thread that terminates last; it could be any of the threads.
Alternatively, if we are modeling a graph of connected nodes, we might want to delete a
node when every connection to it is removed from the graph. unique_ptr does not solve
these cases, since there is not a single owner for the object.
shared_ptr can be used in such situations: shared_ptr can be copied many times,
and the object pointed to by the pointer will remain alive until the last shared_ptr is
destroyed. We guarantee that the object remains valid as long as there is at least one
shared_ptr instance pointing to it.
Let's look at an example making use of it:
class Node {
public:
void addConnectedNode(std::shared_ptr<Node> node);
void removeConnectedNode(std::shared_ptr<Node> node);

private:
std::vector<std::shared_ptr<Node>>d_connections;
};
Here, we can see that we are holding many shared_ptr instance to nodes. If we have
a shared_ptr instance to a node, we want to be sure that the node exists, but when
we remove the shared pointer, we do not care about the node anymore: it might be
deleted, or it might be kept alive if there is another node connected to it.
Similar to the unique_ptr counterpart, when we want to create a new node, we can
use the std::make_shared function, which takes the type of the object to construct as
the template argument and the arguments to pass to the constructor of the object and
returns shared_ptr to the object.
276 | Object-Oriented Programming

You might notice that there might be a problem in the example we showed: what
happens if node A is connected to node B and node B is connected to node A?
Both nodes have a shared_ptr instance to the other, and even if no other node has a
connection to them, they will remain alive because a shared_ptr instance to them exists.
This is an example of circular dependency.
When using shared pointers, we must pay attention to these cases. The standard library
offers a different kind of pointer to handle these situations: std::weak_ptr (read as weak
pointer).
weak_ptr is a smart pointer that can be used in conjunction with shared_ptr to solve the
circular dependencies that might happen in our programs.
Generally, shared_ptr is enough to model most cases where unique_ptr does not work,
and together they cover the majority of the uses of dynamic memory in a code base.
Lastly, we are not helpless if we want to use dynamic memory for arrays of which we
know the size only at runtime. unique_ptr can be used with array types, and shared_ptr
can be used with array types starting from C++17:
std::unique_ptr<int[]>ints = std::make_unique<int[]>();
std::shared_ptr<float[]>floats = std::make_shared<float[]>();

Activity 26: Creating a Factory for UserProfileStorage


Our code needs to create new instances of the UserProfileStorage interface we wrote
during Activity 25: Retrieving User Information:
1. Write a new UserProfileStorageFactory class. Now create a new create method
which returns a UserProfileStorage:
2. In the UserProfileStorageFactory class, return unique_ptr so that it manages the
lifetime of the interface:
class UserProfileStorageFactory {
public:
std::unique_ptr<UserProfileStorage> create() const {
// Create the storage and return it
}
};
Safe and Easy Dynamic Memory | 277

3. Now, in the main function, call the UserProfileStorageFactory class.

Note
The solution for this activity can be found at page 313.

Activity 27: Using a Database Connection for Multiple Operations


In our online store, after a user has paid for a purchase, we want to update their order
list so that it is displayed on their profile. At the same time, we also need to schedule
the processing of the order.
To do so, we need to update the records in our database.
We don't want to wait for one operation to perform the other, so we process the
updates in parallel:
1. Let's create a DatabaseConnection class that can be used in parallel. We want to
reuse this as much as possible, and we know we can use std::async to start a new
parallel task.
2. Assuming that there are two functions, updateOrderList(DatabaseConnection&)
and scheduleOrderProcessing(DatabaseConnection&), write two functions,
updateWithConnection() and scheduleWithConnection() which take a shared pointer
to DatabaseConnection and call the respective function defined above:
void updateWithConnection(std::shared_ptr<DatabaseConnection> connection)
{
updateOrderList(*connection);
}
void scheduleWithConnection(std::shared_ptr<DatabaseConnection>
connection) {
scheduleOrderProcessing(*connection);
}
3. Use shared_ptr and keep a copy of shared_ptr in order to make sure that the
connection remains valid.
278 | Object-Oriented Programming

4. Now let's write the main function, where we create a shared pointer to the
connection and then we call std::async with the two functions we defined above,
as illustrated:
int main()
{
std::shared_ptr<DatabaseConnection> connection = std::make_
shared<DatabaseConnection>();
std::async(std::launch::async, updateWithConnection, connection);
std::async(std::launch::async, scheduleWithConnection, connection);
}
The output is as follows:
Updating order and scheduling order processing in parallel
Schedule order processing
Updating order list

Note
The solution for this activity can be found at page 314.

Summary
In this chapter, we saw how inheritance can be used to combine classes in C++. We saw
what a base class is and what a derived class is, how to write a class that derives from
another, and how to control the visibility modifier. We talked about how to initialize a
base class in a derived one by calling the base class constructor.
We then explained polymorphism and the ability of C++ to dynamically bind a pointer
or reference of a derived class to a pointer or reference of the base class. We explained
what dispatch for functions is, how it works statically by default, and how we can make
it dynamic with the use of the virtual keyword. Following that, we explored how to
properly write virtual functions and how we can override them, making sure to mark
such overrode functions with the override keyword.
Next, we showed how to define interfaces with abstract base classes and how to
use pure virtual methods. We also provided guidelines on how to correctly define
interfaces.
Summary | 279

Lastly, we delved into dynamic memory and what problems it solves, but we also saw
how easy it is to use it incorrectly.
We concluded the chapter by showing how modern C++ makes using dynamic memory
painless by providing smart pointers that handle complex details for us: unique_ptr
to manage objects with a single owner, and shared_ptr for objects owned by multiple
objects.
All these tools can be effective at writing solid programs that can be effectively evolved
and maintained, while retaining the performance C++ is famous for.
Appendix
>
About

This section is included to assist the students to perform the activities in the book.
It includes detailed steps that are to be performed by the students to achieve the objectives of
the activities.
282 | Appendix

Lesson 1: Getting Started

Activity 1: Find the Factors of 7 between 1 and 100 Using a while Loop
1. Import all the required header files before the main function:
#include <iostream>
2. Inside the main function, create a variable i of type unsigned, and initialize its value
as 1:
unsigned i = 1;
3. Now, use the while loop adding the logic where the value of i should be less than
100:
while ( i < 100){ }
4. In the scope of the while loop, use the if statement with the following logic:
if (i%7 == 0) {
std::cout << i << std::endl;
}
5. Increase the value of the i variable to iterate through the while loop to validate the
condition:
i++;
The output of the program is as follows:
7
14
21
28
...
98

Activity 2: Define a Bi-Dimensional Array and Initialize Its Elements


1. After creating a C++ file, include the following header file at the start of the
program:
#include <iostream>
Lesson 1: Getting Started | 283

2. Now, in the main function, create a bi-directional array named foo of type integer,
with three rows and three columns, as shown here:
int main()
{
int foo[3][3];
3. Now, we will use the concept of a nested for loop to iterate through each index
entry of the foo array:
for (int x= 0; x < 3; x++){
for (int y = 0; y < 3; y++){

}
}
4. In the second for loop, add the following statement:
foo[x][y] = x + y;
5. Finally, iterate over the array again to print its values:
for (int x = 0; x < 3; x++){
for (int y = 0; y < 3; y++){
std::cout << “foo[“ << x << “][“ << y << “]: “ << foo[x][y] <<
std::endl;
}
}
The output is as follows:
foo[0][0]: 0
foo[0][1]: 1
foo[0][2]: 2
foo[1][0]: 1
foo[1][1]: 2
foo[1][2]: 3
foo[2][0]: 2
foo[2][1]: 3
foo[2][2]: 4
284 | Appendix

Lesson 2: Functions

Activity 3: Calculating if a Person is Eligible to Vote or Not


1. Include the header file in the program to print the output as shown here:
#include <iostream>
2. Now, create a function named byreference_age_in_5_years and the if loop with
the following condition to print the message:
void byreference_age_in_5_years(int& age) {
if (age >= 18) {
std::cout << “Congratulations! You are eligible to vote for your
nation.” << std::endl;
return;
3. Add the else block to provide another condition if the age of the user is less than
18 years:
} else{
int reqAge = 18;
int yearsToGo = reqAge-age;
std::cout << “No worries, just “<< yearsToGo << “ more years to go.”
<< std::endl;
}
}
4. In the main function, create a variable of type integer and pass it as a reference in
the byreference_age_in_5_years function as shown:
int main() {
int age;
std::cout << “Please enter your age:”;
std::cin >> age;
byreference_age_in_5_years(age);
}
Lesson 2: Functions | 285

Activity 4: Apply the Understanding of Passing by Reference or Value in Func-


tions
1. After adding all the required header files, create the first function of type integer
as shown here:
int sum(int a, int b)
{
return a + b
}
Take by value, return by value, since the types are small in memory and there is no
reason to use references.
2. The second function should be written as follows:
int& getMaxOf(std::array<int, 10>& array1, std::array<int, 10>& array2,
int index) {
if (array1[index] >= array2[index]) {
return array1[index];
} else {
return array2[index];
}
}

Activity 5: Organizing Functions in Namespaces


1. Include the required header file and namespace to print the required output:
#include <iostream>
using namespace std;
2. Now, create a namespace named LamborghiniCar with the following output func-
tion:
namespace LamborghiniCar
{
int output(){
std::cout << “Congratulations! You deserve the Lamborghini.” <<
std::endl;
return NULL;
}
}
286 | Appendix

3. Create another namespace named PorscheCar and add an output function as


shown:
namespace PorscheCar
{
int output(){
std::cout << “Congratulations! You deserve the Porsche.” << std::endl;
return NULL;
}
}
In the main function, create a variable named magicNumber of type integer to accept the
input from the user:
int main()
{
int magicNumber;
std::cout << “Select a magic number (1 or 2) to win your dream car: “;
std::cin >> magicNumber;
1. Add the following conditional if…else-if…else statement to complete the
program:
if (magicNumber == 1){
std::cout << LamborghiniCar::output() << std::endl;
} else if(magicNumber == 2){
std::cout << PorscheCar::output() << std::endl;
}else{
std::cout << “Please type the correct magic number.” << std::endl;
}
}

Activity 6: Writing a Math Library for use in a 3D Game


1. Add the required header files at the start of the program (mathlib.h file is
provided):
#include <mathlib.h>
#include <array>
#include <iostream>
2. Create a global const variable of type float as shown here:
const float ENEMY_VIEW_RADIUS_METERS = 5;
Lesson 2: Functions | 287

3. In the main function, create two arrays of type float and assign the following
values:
int main() {
std::array<float, 3> enemy1_location = {2, 2 ,0};
std::array<float, 3> enemy2_location = {2, 4 ,0};
4. Now, create a variable named enemy_distance of type float and use the distance
function to assign the value after calculating it:
float enemy_distance = johnny::mathlib::distance(enemy1_location,
enemy2_location);
float distance_from_center = johnny::mathlib::distance(enemy1_
location);
5. Using the circumference function of mathlib.h, calculate and assign the enemy
visual radius to view_circumference_for_enemy of type float:
using johnny::mathlib::circumference;
float view_circumference_for_enemy = circumference(ENEMY_VIEW_RADIUS_
METERS);
6. Create a variable named total_distance of type float and assign the distance
difference between the two enemies as shown in the following code:
float total_distance = johnny::mathlib::total_walking_distance({
enemy1_location,
{2, 3, 0}, // y += 1
{2, 3, 3}, // z += 3
{5, 3, 3}, // x += 3
{8, 3, 3}, // x += 3
{8, 3, 2}, // z -= 1
{2, 3, 2}, // x -= 6
{2, 3, 1}, // z -= 1
{2, 3, 0}, // z -= 1
enemy2_location
});
7. Print the output using the following print statement:
std::cout << “The two enemies are “ << enemy_distance << “m apart and
can see for a circumference of “
<< view_circumference_for_enemy << “m. To go to from one to
the other they need to walk “
<< total_distance << “m.”;
}
288 | Appendix

Lesson 3: Classes

Activity 7: Information Hiding Through Getters and Setters


1. Define a class named Coordinates with its members under a private access speci-
fier:
class Coordinates {
private:
float latitude;
float longitude;
};
2. Add the four operations as specified above and make them publicly accessible by
preceding their declaration with the public access specifier. The setters (set_lati-
tude and set_longitude) should take an int as a parameter and return void, while
the getters do not take any parameter and return a float:
class Coordinates {
private:
float latitude;
float longitude;

public:
void set_latitude(float value){}
void set_longitude(float value){}
float get_latitude(){}
float get_longitude(){}
};
3. The four methods should now be implemented. The setters assign the given value
to the corresponding members they are supposed to set; the getters return the
values that are stored.
class Coordinates {
private:
float latitude;
float longitude;
Lesson 3: Classes | 289

public:
void set_latitude(float value){ latitude = value; }
void set_longitude(float value){ longitude = value; }
float get_latitude(){ return latitude; }
float get_longitude(){ return longitude; }
};
An example is as follows:
#include <iostream>

int main() {
Coordinates washington_dc;
std::cout << “Object named washington_dc of type Coordinates created.”
<< std::endl;

washington_dc.set_latitude(38.8951);
washington_dc.set_longitude(-77.0364);
std::cout << “Object’s latitude and longitude set.” << std::endl;

std::cout << “Washington DC has a latitude of “


<< washington_dc.get_latitude()
<< “ and longitude of “ << washington_dc.get_longitude() << std::endl;
}

Activity 8: Representing Positions in a 2D Map


1. The first step is to create a class named Coordinates containing the coordinates
as data members. These are two floating-point values, _latitude and _longitude,
which identify the coordinates on a geographic coordinate system. Additionally,
these data members are initialized with a private access specifier:
class Coordinates {
private:
float _latitude;
float _longitude;
};
290 | Appendix

2. Then, the class is extended with a public constructor which takes two arguments
used to initialize the data members of the class:
class Coordinates {
public:
Coordinates(float latitude, float longitude)
: _latitude(latitude), _longitude(longitude) {}

private:
int _latitude;
int _longitude;
};
3. We can also add getters as seen previously to access the class members. An
example is as follows:
#include <iostream>

int main() {
Coordinates washington_dc(38.8951, -77.0364);
std::cout << “Object named washington_dc of type Coordinates created.”
<< std::endl;

std::cout << “Washington DC has a latitude of “


<< washington_dc.get_latitude()
<< “ and longitude of “ << washington_dc.get_longitude()
<< std::endl;
}

Activity 9: Storing Multiple Coordinates of Different Positions in the Map


1. Using the RAII programming idiom, write a class that manages memory allocation
and deletion of an array of int. The class has an array of integers as member data,
which will be used to store the values.
The constructor takes the size of the array as a parameter.
The constructor also takes care of allocating memory, which is used to store the
coordinates.
2. Finally, define a destructor and make sure to free the previously allocated array in
its implementation.
Lesson 3: Classes | 291

3. We can add print statements to visualize what is happening:


class managed_array {
public:
explicit managed_array(size_t size) {
array = new int[size];
std::cout << “Array of size “ << size << “ created.” << std::endl;
}

~managed_array() {
delete[] array;
std::cout << “Array deleted.” << std::endl;
}

private:
int *array;
};
4. We can use our managed_array class as follows:
int main() {
managed_array m(10);
}
The output will be as follows:
Array of size 10 created.
Array deleted.

Activity 10: The AppleTree Class, which Creates an Apple Instance


1. First, we need to create a class with a private constructor. In this way, the object
cannot be constructed, because the constructor is not publicly accessible:
class Apple
{
private:
Apple() {}
// do nothing
};
292 | Appendix

2. The AppleTree class is defined and contains a method called createFruit that is in
charge of creating an Apple and returning it:
#include <iostream>

class AppleTree
{
public:
Apple createFruit(){
Apple apple;
std::cout << “apple created!” << std::endl;
return apple;
}
};
3. If we compile this code, we will get an error. At this point, the Apple constructor
is private, so the AppleTree class cannot access it. We need to declare the Apple-
Tree class as a friend of Apple to allow AppleTree to access the private methods of
Apple:
class Apple
{
friend class AppleTree;
private:
Apple() {}
// do nothing
}
4. The Apple object can now be constructed using the following code:
int main() {
AppleTree tree;
Apple apple = tree.createFruit();
}
This prints the following:
apple created!
Lesson 3: Classes | 293

Activity 11: Ordering Point Objects


1. We need to add an overload for the < operator to the Point class that we have
previously defined. This takes another object of type Point as an argument and
returns a Boolean indicating whether the object is less than the one provided as
the parameter, using the previous definition for how to compare the two points:
class Point
{
public:
bool operator< (const Point &other){
return x < other.x || (x == other.x && y < other.y);
}

int x;
int y;
};
2. At this point, we are able to compare the two Point objects:
#include <iostream>

int main() {
Point p_1, p_2;

p_1.x = 1;
p_1.y = 2;

p_2.x = 2;
p_2.y = 1;

std::cout << std::boolalpha << (p_1 < p_2) << std::endl;


}
3. Since in our example p_1.x is initialized to 1 and p_2.x to 2, the result of the
comparison will be true, which indicates that p_1 comes earlier than p_2 in the
order.
294 | Appendix

Activity 12: Implementing Functors


1. Define a class constituted by a private data member of type int and add a
constructor to initialize it:
class AddX {
public:
AddX(int x) : x(x) {}

private:
int x;
};
2. Extend it with the call operator operator() which takes an int as a parameter and
returns an int. The implementation in the function body should return the addi-
tion of the previously defined x value and the parameter of the function named y:
class AddX {
public:
AddX(int x) : x(x) {}
int operator() (int y) { return x + y; }

private:
int x;
};
3. Instantiate an object of the class just defined and invoke the call operator:
int main() {
AddX add_five(5);
std::cout << add_five(4) << std::endl;
}
The output will be as follows:
9
Lesson 04: Generic Programming and Templates | 295

Lesson 04: Generic Programming and Templates

Activity 13: Read Objects from a Connection


1. We start by including the headers of the files that provided the connection and the
user account object:
#include <iostream>
#include <connection.h>
#include <useraccount.h>
2. We can then start to write the writeObjectToConnection function. Declare a
template which takes two typename parameters: an Object and a Connection. Call
the static method serialize() on the object to get the std::array representing
the object, then call writeNext() on the connection to write the data to it:
template<typename Object, typename Connection>
void writeObjectToConnection(Connection& con, const Object& obj) {
std::array<char, 100> data = Object::serialize(obj);
con.writeNext(data);
}
3. We can then write readObjectFromConnection. Declare a template taking the same
two parameters as before: an Object and a Connection. Inside, we call the connec-
tion readNext() to get the data stored inside the connection, then we call the
static method on the object type deserialize() to get an instance of the object
and return it:
template<typename Object, typename Connection>
Object readObjectFromConnection(Connection& con) {
std::array<char, 100> data = con.readNext();
return Object::deserialize(data);
}
4. Finally, in the main function, we can call the functions we created to serialize
objects. Both with TcpConnection:
std::cout << “serialize first user account” << std::endl;
UserAccount firstAccount;
TcpConnection tcpConnection;
writeObjectToConnection(tcpConnection, firstAccount);
UserAccount transmittedFirstAccount =
readObjectFromConnection<UserAccount>(tcpConnection);
296 | Appendix

5. And with UdpConnection:


std::cout << “serialize second user account” << std::endl;
UserAccount secondAccount;
UdpConnection udpConnection;
writeObjectToConnection(udpConnection, secondAccount);
UserAccount transmittedSecondAccount =
readObjectFromConnection<UserAccount>(udpConnection);
The output of the program is as follows:
serialize first user account
the user account has been serialized
the data has been written
the data has been read
the user account has been deserialized

serialize second user account


the user account has been serialized
the data has been written
the data has been read
the user account has been deserialized

Activity 14: UserAccount to Support Multiple Currencies


1. We start by including the file defining the currencies:
#include <currency.h>
#include <iostream>
2. We then declare the template class Account. It should take a template parameter:
Currency. We store the current balance of the account inside a data member of
type Currency. We also provide a method in order to extract the current value of
the balance:
template<typename Currency>
class Account {
public:
Account(Currency amount) : balance(amount) {}

Currency getBalance() const {


return balance;
Lesson 04: Generic Programming and Templates | 297

private:
Currency balance;
};
3. Next, we create the method addToBalance. It should be a template with one type
parameter, the other currency. The method takes a value of OtherCurrency and
converts it to the value of the currency of the current account with the to() func-
tion, specifying to which currency the value should be converted to. It then adds it
to the balance:
template<typename OtherCurrency>
void addToBalance(OtherCurrency amount) {
balance.d_value += to<Currency>(amount).d_value;
}
4. Finally, we can try to call our class in the main function with some data:
Account<GBP> gbpAccount(GBP(1000));
// Add different currencies
std::cout << “Balance: “ << gbpAccount.getBalance().d_value << “ (GBP)” <<
std::endl;
gbpAccount.addToBalance(EUR(100));
std::cout << “+100 (EUR)” << std::endl;
std::cout << “Balance: “ << gbpAccount.getBalance().d_value << “ (GBP)” <<
std::endl;
The output of the program is:
Balance: 1000 (GBP)
+100 (EUR)
Balance: 1089 (GBP)
298 | Appendix

Activity 15: Write a Matrix Class for Mathematical Operations in a Game


1. We start by defining a Matrix class which takes three template parameters: one
type and the two dimensions of the Matrix class. The dimensions are of type int.
Internally, we create a std::array with the size of the number of rows times the
number of columns, in order to have enough space for all elements of the matrix.
We add a constructor to initialize the array to empty, and a constructor to provide
a list of values:
#include <array>

template<typename T, int R, int C>


class Matrix {
// We store row_1, row_2, ..., row_C
std::array<T, R*C> data;
public:
Matrix() : data({}) {}
Matrix(std::array<T, R*C> initialValues) : data(initialValues) {}
};
2. We add a method get() to the class to return a reference to the element T. The
method needs to take the row and column we want to access.
3. We make sure that the requested indexes are inside the bounds of the matrix,
otherwise we call std::abort(). In the array, we first store all the elements of the
first row, then all the elements of the second row, and so on. When we want to
access the elements of the nth row, we need to skip all the elements of the previ-
ous rows, which are going to be the number of elements per row (so the number
of columns) times the previous rows, resulting in the following method:
T& get(int row, int col) {
if (row >= R || col >= C) {
std::abort();
}
return data[row*C + col];
}
Lesson 04: Generic Programming and Templates | 299

4. For convenience, we define a function to print the class as well. We print all the
elements in the columns separated by spaces, with one column per line:
template<typename T, size_t R, size_t C>
std::ostream& operator<<(std::ostream& os, Matrix<T, R, C> matrix) {
os << ‘\n’;
for(int r=0; r < R; r++) {
for(int c=0; c < C; c++) {
os << matrix.get(r, c) << ‘ ‘;
}
os << “\n”;
}
return os;
}
5. In the main function, we can now use the functions we have defined:
Matrix<int, 3, 2> matrix({
1, 2,
3, 4,
5, 6
});
std::cout << “Initial matrix:” << matrix << std::endl;
matrix.get(1, 1) = 7;
std::cout << “Modified matrix:” << matrix << std::endl;
The output is as follows:
Initial matrix:
1 2
3 4
5 6

Modified matrix:
1 2
3 7
5 6
300 | Appendix

Solution bonus step:


1. We can add a new method, multiply, which takes a std::array of type T with the
length of C by const reference, since we are not modifying it.
The function returns an array of the same type, but length R.
2. We follow the definition of matrix-vector multiplication to compute the result:
std::array<T, R> multiply(const std::array<T, C>& vector){
std::array<T, R> result = {};
for(size_t r = 0; r < R; r++) {
for(size_t c = 0; c < C; c++) {
result[r] += get(r, c) * vector[c];
}
}
return result;
}
3. We can now extend our main function to call the multiply function:
std::array<int, 2> vector = {8, 9};
std::array<int, 3> result = matrix.multiply(vector);
std::cout << “Result of multiplication: [“ << result[0] << “, “
<< result[1] << “, “ << result[2] << “]” << std::endl;
The output is as follows:
Result of multiplication: [26, 87, 94]

Activity 16: Make the Matrix Class Easier to Use


1. We start by importing <functional> in order to have access to std::multiplies:
#include <functional>
2. We then change the order of the template parameters in the class template, so
that the size parameters come first. We also add a new template parameter, Multi-
ply, which is the type we will use for computing the multiplication between the
elements in the vector by default, and we store an instance of it in the class:
template<int R, int C, typename T = int, typename
Multiply=std::multiplies<T> >
class Matrix {
std::array<T, R*C> data;
Lesson 04: Generic Programming and Templates | 301

Multiply multiplier;
public:
Matrix() : data({}), multiplier() {}
Matrix(std::array<T, R*C> initialValues) : data(initialValues),
multiplier() {}
};
The get() function remains the same as the previous activity.
3. We now need to make sure that the Multiply method uses the Multiply type
provided by the user to perform the multiplication.
4. To do so, we need to make sure to call multiplier(operand1, operand2) instead of
operand1 * operand2, so that we use the instance we stored inside the class:
std::array<T, R> multiply(const std::array<T, C>& vector) {
std::array<T, R> result = {};
for(int r = 0; r < R; r++) {
for(int c = 0; c < C; c++) {
result[r] += multiplier(get(r, c), vector[c]);
}
}
return result;
}
5. We can now add an example of how we can use the class:
// Create a matrix of int, with the ‘plus’ operation by default
Matrix<3, 2, int, std::plus<int>> matrixAdd({
1, 2,
3, 4,
5, 6
});

std::array<int, 2> vector = {8, 9};


// This will call std::plus when doing the multiplication
std::array<int, 3> result = matrixAdd.multiply(vector);
std::cout << “Result of multiplication(with +): [“ << result[0] << “, “
<< result[1] << “, “ << result[2] << “]” << std::endl;
The output is as follows:
Result of multiplication(with +): [20, 24, 28]
302 | Appendix

Activity 17: Ensure Users are Logged in When Performing Actions on the Ac-
count
1. We first declare a template function which takes two type parameters: an Action
and a Parameter type.
2. The function should take the user identification, the action and the parameter.
The parameter should be accepted as a forwarding reference. As a first step, it
should check if the user is logged in, by calling the isLoggenIn() function. If the
user is logged in, it should call the getUserCart() function, then call the action
passing the cart and forwarding the parameter:
template<typename Action, typename Parameter>
void execute_on_user_cart(UserIdentifier user, Action action, Parameter&&
parameter) {
if(isLoggedIn(user)) {
Cart cart = getUserCart(user);
action(cart, std::forward<Parameter>(parameter));
} else {
std::cout << “The user is not logged in” << std::endl;
}
}
3. We can test how execute_on_user_cart works by calling it in the main function:
Item toothbrush{1023};
Item toothpaste{1024};

UserIdentifier loggedInUser{0};
std::cout << “Adding items if the user is logged in” << std::endl;
execute_on_user_cart(loggedInUser, addItems,
std::vector<Item>({toothbrush, toothpaste}));

UserIdentifier loggedOutUser{1};
std::cout << “Removing item if the user is logged in” << std::endl;
execute_on_user_cart(loggedOutUser, removeItem, toothbrush);
The output is as follows:
Adding items if the user is logged in
Items added
Removing item if the user is logged in
The user is not logged in
Lesson 04: Generic Programming and Templates | 303

Activity 18: Safely Perform Operations on User Cart with an Arbitrary Number
of Parameters
1. We need to expand the previous activity to accept any number of parameters with
any kind of ref-ness and pass it to the action provided. To do so, we need to create
a variadic template.
2. Declare a template function that takes an action and a variadic number of param-
eters as template parameters. The function parameters should be the user action,
the action to perform, and the expanded template parameter pack, making sure
that the parameters are accepted as forwarding references.
3. Inside the function, we perform the same checks as before, but now we expand
the parameters when we forward them to the action:
template<typename Action, typename... Parameters>
void execute_on_user_cart(UserIdentifier user, Action action,
Parameters&&... parameters) {
if(isLoggedIn(user)) {
Cart cart = getUserCart(user);
action(cart, std::forward<Parameters>(parameters)...);
} else {
std::cout << “The user is not logged in” << std::endl;
}
}
4. Let’s test the new function in our main function:
Item toothbrush{1023};
Item apples{1024};

UserIdentifier loggedInUser{0};
std::cout << “Replace items if the user is logged in” << std::endl;
execute_on_user_cart(loggedInUser, replaceItem, toothbrush, apples);

UserIdentifier loggedOutUser{1};
std::cout << “Replace item if the user is logged in” << std::endl;
execute_on_user_cart(loggedOutUser, removeItem, toothbrush);
304 | Appendix

The output is as follows:


Replace items if the user is logged in
Replacing item
Item removed
Items added
Replace item if the user is logged in
The user is not logged in

Lesson 5: Standard Library Containers and Algorithms

Activity 19: Storing User Accounts


1. First, we include the header files for the array class and input/output operations
with the required namespace:
#include <array>
2. An array of ten elements of type int is declared:
array<int,10> balances;
3. Initially, the values of the elements are undefined since it is an array of the funda-
mental data type int. The array is initialized using a for loop, where each element
is initialized with its index. The operator size() is used to evaluate the size of the
array and the subscript operator [ ] is used to access every position of the array:
for (int i=0; i < balances.size(); ++i)
{
balances[i] = 0;
}
4. We now want to update the value for the first and last user. We can use front()
and back() to access the accounts of these users:
balances.front() += 100;
balances.back() += 100;
We would like to store the account balance of an arbitrary number of users. We
then want to add 100 users to the account list, with a balance of 500.
5. We can use vector to store an arbitrary number of users. It is defined in the
<vector> header:
#include <vector>
Lesson 5: Standard Library Containers and Algorithms | 305

6. Then, we declare a vector of type int. Optionally, we reserve enough memory to


store the 100 users’ account by calling reserve(100) to avoid memory reallocation:
std::vector<int> balances;
balances.reserve(100);
7. Finally, we modify the for loop to add the balance for the users at the end of the
accounts vector:
for (int i=0; i<100; ++i)
{
balances.push_back(500);
}

Activity 20: Retrieving a User’s Balance from their Given Username


1. Include the header file for the map class and the header for string:
#include <map>
#include <string>
2. Create a map with the key being std::string and the value int:
std::map<std::string, int> balances;
3. Insert the balances of the users inside map by using insert and std::make_pair. The
first argument is the key, the second one is the value:
balances.insert(std::make_pair(“Alice”,50));
balances.insert(std::make_pair(“Bob”, 50));
balances.insert(std::make_pair(“Charlie”, 50));
4. Use the find function providing the name of the user to find the position of the
account in the map. Compare it with end() to check whether a position was found:
auto donaldAccountPos = balances.find(“Donald”);
bool hasAccount = (donaldAccountPos != balances.end());
std::cout << “Donald has an account: “ << hasAccount << std::endl;
5. Now, look for the account of Alice. We know Alice has an account, so there is no
need to check whether we found a valid position. We can print the value of the
account using ->second:
auto alicePosition = balances.find(“Alice”);
std::cout << “Alice balance is: “ << alicePosition->second << std::endl;
306 | Appendix

Activity 21: Processing User Registration in Order


1. First, we include the header file for the stack class:
#include <stack>
2. Create a stack providing the type to store:
std::stack<RegistrationForm> registrationForms;
3. We start by storing the form inside the stack when the user registers. In the body
of the storeRegistrationForm function, push the element into the queue:
stack.push(form);
std::cout << “Pushed form for user “ << form.userName << std::endl;
4. Now, inside endOfDayRegistrationProcessing, we get all the elements inside the
stack and then process them. Use the top() method to access the top element in
the stack and pop() to remove the top element. We stop getting and removing the
first element when no element is left:
while(not stack.empty()) {
processRegistration(stack.top());
stack.pop();
}
5. Finally, we call our functions with some test data:
int main(){
std::stack<RegistrationForm> registrationForms;
storeRegistrationForm(registrationForms, RegistrationForm{“Alice”});
storeRegistrationForm(registrationForms, RegistrationForm{“Bob”});
storeRegistrationForm(registrationForms, RegistrationForm{“Charlie”});
endOfDayRegistrationProcessing(registrationForms);
}

Activity 22: Airport System Management


1. We start by creating the class for Airplane. Make sure to first include the header
for variant:
#include <variant>
Lesson 5: Standard Library Containers and Algorithms | 307

2. Then, create the class with a constructor that sets the current state of the airplane
to AtGate:
class Airplane {
std::variant<AtGate, Taxi, Flying> state;
public:
Airplane(int gate) : state(AtGate{gate}) {
std::cout << “At gate “ << gate << std::endl;
}
};
3. Now, implement the startTaxi() method. First, check the current state of the
airplane with std::holds_alternative<>(), and if the airplane is not in the correct
state, write an error message and return.
4. If the airplane is in the correct state, change the state to taxi by assigning it to the
variant:
void startTaxi(int lane, int numPassengers) {
if (not std::holds_alternative<AtGate>(state)) {
std::cout << “Not at gate: the plane cannot start taxi to lane “
<< lane << std::endl;
return;
}
std::cout << “Taxing to lane “ << lane << std::endl;
state = Taxi{lane, numPassengers};
}
5. We repeat the same process for the takeOff() method:
void takeOff(float speed) {
if (not std::holds_alternative<Taxi>(state)) {
std::cout << “Not at lane: the plane cannot take off with speed “
<< speed << std::endl;
return;
}
std::cout << “Taking off at speed “ << speed << std::endl;
state = Flying{speed};
}
308 | Appendix

6. We can now start looking at the currentStatus() method. Since we want to


perform an operation for each of the states in the variant, we can use a visitor.
7. Outside the Airplane class, create a class that has a method operator() for each
of the types in the airplane state. Inside the method, print the information of the
state. Remember to make the methods public:
class AirplaneStateVisitor {
public:
void operator()(const AtGate& atGate) {
std::cout << “AtGate: “ << atGate.gate << std::endl;
}

void operator()(const Taxi& taxi) {


std::cout << “Taxi: lane “ << taxi.lane << “ with “ << taxi.
numPassengers << “ passengers” << std::endl;
}

void operator()(const Flying& flying) {


std::cout << “Flaying: speed “ << flying.speed << std::endl;
}
};
8. Now, create the currentStatus() method and call the visitor on the state using
std::visit:
void currentStatus() {
AirplaneStateVisitor visitor;
std::visit(visitor, state);
}
9. We can now try to call the functions of Airplane from the main function:
int main()
{
Airplane airplane(52);
airplane.currentStatus();
airplane.startTaxi(12, 250);
airplane.currentStatus();
airplane.startTaxi(13, 250);
airplane.currentStatus();
airplane.takeOff(800);
airplane.currentStatus();
airplane.takeOff(900);
}
Lesson 6: Object-Oriented Programming | 309

Lesson 6: Object-Oriented Programming

Activity 23: Creating Game Characters


1. Create a Character class that has a public method moveTo that prints Moved to
position:
class Character {
public:
void moveTo(Position newPosition) {
position = newPosition;
std::cout << “Moved to position “ << newPosition.positionIdentifier
<< std::endl;
}
private:
Position position;
};
2. Create a struct named Position:
struct Position {
// Fields to describe the position go here
std::string positionIdentifier;
};
3. Create two classes Hero and Enemy that are derived from the class Character:
// Hero inherits publicly from Character: it has
// all the public member of the Character class.
class Hero : public Character {

};

// Enemy inherits publicly from Character, like Hero


class Enemy : public Character {

};
4. Create a class Spell with the constructor that prints the name of the person
casting the spell:
class Spell {
public:
Spell(std::string name) : d_name(name) {}

std::string name() const {


310 | Appendix

return d_name;
}
private:
std::string d_name;
};
5. The class Hero should have a public method to cast a spell. Use the value from the
Spell class:
public:
void cast(Spell spell) {
// Cast the spell
std::cout << “Casting spell “ << spell.name() << std::endl;
}
6. The class Enemy should have a public method to swing a sword which prints Swing-
ing sword:
public:
void swingSword() {
// Swing the sword
std::cout << “Swinging sword” << std::endl;
}
7. Implement the main method that calls these methods in various classes:
int main()
{
Position position{“Enemy castle”};
Hero hero;
Enemy enemy;

// We call moveTo on Hero, which calls the method inherited


// from the Character class
hero.moveTo(position);
enemy.moveTo(position);

// We can still use the Hero and Enemy methods


hero.cast(Spell(“fireball”));
enemy.swingSword();
}
Lesson 6: Object-Oriented Programming | 311

Activity 24: Calculating Employee Salaries


1. We can create a class Employee with two virtual methods, getBaseSalary and getBo-
nus, since we want to change those methods based on the type of employee:
class Employee {
public:
virtual int getBaseSalary() const { return 100; }
virtual int getBonus(const Deparment& dep) const {
if (dep.hasReachedTarget()) {
}
return 0;
}
2. We also define a method, getTotalComp, which does not need to be virtual, but will
call the two virtual methods:
int getTotalComp(const Deparment& dep) {

}
};
3. We then derive a Manager class from it, overriding the method for computing the
bonus. We might also want to override getBaseSalary if we want to give a different
base salary to managers:
class Manager : public Employee {
public:
virtual int getBaseSalary() const override { return 150; }
virtual int getBonus(const Deparment& dep) const override {
if (dep.hasReachedTarget()) {
int additionalDeparmentEarnings = dep.effectiveEarning() - dep.
espectedEarning();
return 0.2 * getBaseSalary() + 0.01 * additionalDeparmentEarnings;
}
return 0;
}
};
312 | Appendix

4. Create a class Department as shown:


class Department {
public:
bool hasReachedTarget() const {return true;}
int espectedEarning() const {return 1000;}
int effectiveEarning() const {return 1100;}
};

5. Now, in the main function, call the Department, Employee, and Manager classes as
shown:
int main()
{
Department dep;
Employee employee;
Manager manager;
std::cout << “Employee: “ << employee.getTotalComp(dep) << “. Manager: “
<< manager.getTotalComp(dep) << std::endl;
}

Activity 25: Retrieving User Information


1. We have to write the code that can be independent of where the data is coming
from. So, we create an interface UserProfileStorage for retrieving the Customer-
Profile from a UserId:
struct UserProfile {};
struct UserId {};

class UserProfileStorage {
public:
virtual UserProfile getUserProfile(const UserId& id) const = 0;

virtual ~UserProfileStorage() = default;

protected:
UserProfileStorage() = default;
UserProfileStorage(const UserProfileStorage&) = default;
UserProfileStorage& operator=(const UserProfileStorage&) = default;
};
Lesson 6: Object-Oriented Programming | 313

2. Now, write the UserProfileCache class that inherits from UserProfileStorage:

class UserProfileCache : public UserProfileStorage {


public:
UserProfile getUserProfile(const UserId& id) const override {
std::cout << “Getting the user profile from the cache” << std::endl;
return UserProfile(); }
};
void exampleOfUsage(const UserProfileStorage& storage) {
UserId user;
std::cout << “About to retrieve the user profile from the storage”
<<std::endl;
UserProfile userProfile = storage.getUserProfile(user);
}

3. In the main function, call the UserProfileCache class and exampleOfUsage function
as shown:
int main()
{
UserProfileCache cache;
exampleOfUsage (cache);
}

Activity 26: Creating a Factory for UserProfileStorage


1. Write the following code that needs the UserProfileStorage class, as shown. To
allow that, we provide a factory class, which has a method create that provides an
instance of UserProfileStorage. Write this class making sure that the user does not
have to manage the memory for the interface manually:

#include <iostream>
#include <memory>
#include <userprofile_activity18.h>

class UserProfileStorageFactory {
public:
std::unique_ptr<UserProfileStorage> create() const {
return std::make_unique<UserProfileCache>();
}
};
314 | Appendix

2. We want the UserProfileStorageFactory class to return a unique_ptr so that it


manages the lifetime of the interface:
void getUserProfile(const UserProfileStorageFactory& storageFactory) {
std::unique_ptr<UserProfileStorage> storage = storageFactory.create();
UserId user;
storage->getUserProfile(user);
// The storage is automatically destroyed
}
3. Now, in the main function, call the UserProfileStorageFactory class as shown:
int main()
{
UserProfileStorageFactory factory;
getUserProfile(factory);

Activity 27: Using a Database Connection for Multiple Operations


1. First, create a DatabaseConnection class that can be used in parallel. We want to
reuse it as much as possible, and we know we can use std::async to start a new
parallel task:
#include <future>

struct DatabaseConnection {};


2. Assuming there are two functions updateOrderList(DatabaseConnection&) and
scheduleOrderProcessing(DatabaseConnection&), write a function that creates a
DatabaseConnection and gives it to the two parallel tasks. (Note that we don’t know
which task finishes first):
void updateOrderList(DatabaseConnection&) {}
void scheduleOrderProcessing(DatabaseConnection&) {}
3. You must understand when and how to create a shared_ptr. You can also use the
following code to write the shared_ptr correctly.
/* We need to get a copy of the shared_ptr so it stays alive until this
function finishes */
void updateWithConnection(std::shared_ptr<DatabaseConnection> connection)
{
updateOrderList(*connection);
}
There are several users of the connection, and we do not know which one is the
owner, since the connection needs to stay alive as long as anyone is using it.
Lesson 6: Object-Oriented Programming | 315

4. To model this, we use a shared_ptr. Remember that we need a copy of the shared_
ptr to exist in order for the connection to remain valid:
/* We need to get a copy of the shared_ptr so it stays alive until this
function finishes. */
void scheduleWithConnection(std::shared_ptr<DatabaseConnection>
connection) {
scheduleOrderProcessing(*connection);
}
5. Create the main function as follows:
int main()
{
std::shared_ptr<DatabaseConnection> connection = std::make_
shared<DatabaseConnection>();
std::async(std::launch::async, updateWithConnection, connection);
std::async(std::launch::async, scheduleWithConnection, connection);
}
>
Index

About

All major keywords used in this book are captured alphabetically in this section. Each one is
accompanied by the page number of where they appear.
179, 206, 210, 224-225,
A additional: 21, 72, 79,
154-155, 166, 170, 199, 268, 273-275, 290
ability: 2, 45, 79, 81, 92, 202, 204, 209, 216, 218, arises: 33, 124, 268
101, 145, 160, 179, 218, 245, 261-262, 270 arithmetic: 1, 40, 218
250-251, 274, 278 address: 15-16, 21, arrays: 1, 30, 36, 38-40, 72,
access: 16, 37-38, 40, 96, 119, 121, 210 184, 186, 268, 276, 287
50-53, 57, 61, 76-78, advantage: 73, 101, assign: 40, 62, 67, 70,
83-84, 86-89, 92, 94, 155, 166, 190 84, 91, 99, 121, 123,
96, 99, 108, 111-114, 116, advantages: 40, 44, 84, 203, 209, 215, 247,
130, 138, 145, 148, 151, 130, 144, 168, 254 252, 264, 287-288
155-156, 162, 166, 169, algorithm: 45, 67, 134, 182, assigned: 70, 126, 214, 252
172-173, 175, 178-179, 224-225, 228, 230-235 assignment: 13, 16, 69-70,
182, 184, 186, 188, aliases: 150, 173, 83, 117, 119-121, 123-124,
190-191, 195, 199-201, 175, 177, 179 245, 253, 263-264
209, 211, 215, 217-218, allocate: 37, 111, 159 associate: 90, 195, 236
225-226, 237, 241-242, allocated: 106, 111, 271, 290 associated: 11, 17, 53,
247, 268, 288-290, 292, allocation: 111, 185, 290 86, 90, 93, 98, 111,
298, 300, 304, 306 almost: 44, 174, 207 194-196, 207, 271
account: 137-138, 142-144, amount: 45, 137-138, assume: 4, 89, 121, 201
152-154, 168, 176, 191, 155, 159, 296-297 automatic: 53, 56, 59, 67,
196-197, 234-236, anymore: 32, 167, 178, 70, 106, 266-267, 271
295-297, 302, 304-305 187, 217, 267, 269,
action: 7, 96, 163,
165-172, 174-175,
271-272, 275
anywhere: 22, 65,
B
230, 271, 302-303 87-88, 90, 246, 273 balance: 137, 153-154,
actions: 40, 168-170, 174, applies: 16, 190, 262 176, 191, 196-197,
225, 261-262, 302 approach: 85, 149, 240 224, 234-236,
activity: 36, 40, 63, 72, arbitrary: 58, 143, 168, 296-297, 304-305
78, 80, 98-99, 108-109, 170-171, 174, 182, balances: 176, 197, 223,
111, 116-117, 127-130, 191, 240, 303-304 234-236, 304-305
151, 153-154, 156-157, argument: 58-61, 63-64, because: 5, 15, 44, 51, 53,
162-163, 168, 170, 71-72, 75, 97, 103, 106, 60, 67, 72-73, 76-77,
174-175, 191, 196-197, 117, 121, 127, 139-140, 79-80, 100-101, 113,
201-202, 214, 249-250, 153, 158, 160-163, 116, 124, 136-137, 144,
258, 260, 264-265, 173-174, 179, 193, 150, 161, 167-168, 177,
276-278, 282, 284-286, 197, 210, 226, 270, 193, 196-198, 210, 219,
288-291, 293-296, 298, 273, 275, 293, 305 221, 224, 243, 250-251,
300-306, 309, 311-314 arguments: 9, 43, 46, 253, 257, 261, 264, 268,
actual: 4, 15-16, 186, 226 57-60, 65, 67, 70, 72-73, 271, 273-274, 276, 291
addition: 40, 93, 95, 101, 75-76, 79, 81, 108, before: 4-5, 13, 32, 36,
126, 129, 210, 294 128, 136, 139-140, 142, 48, 53-55, 68-69, 76,
158-161, 163, 170-174, 87, 94, 102-104, 127,
129, 136, 141, 158, 161, charge: 116, 272, 292 140-141, 148, 151, 155,
164, 166, 168-169, 184, charlie: 196, 236, 305-306 184, 253-254, 273, 292
200, 211, 223, 244, 253, checked: 29-30, 32, compiler: 3-4, 7, 9, 13-14,
258, 263, 266, 272, 121, 141, 184 18-19, 25, 38-39, 45, 47,
274, 282, 295, 303 checks: 6, 136, 143, 49, 53-54, 58, 66, 75,
behave: 202, 246, 250, 270 155, 170, 184, 303 77-80, 100, 103, 108, 117,
behavior: 38, 58, 79, 93, choice: 2, 210, 274 119-120, 123, 125-126,
100, 124, 200, 250-251, classes: 5, 13, 19, 50-51, 61, 136-137, 139, 141-143,
253, 263, 268 64, 68, 81, 83-84, 87-88, 145, 147-148, 151, 155,
benefit: 110, 147, 166 93, 105-106, 108, 111-113, 159-161, 163, 165, 172,
better: 58, 69, 76-77, 116, 119, 124, 130, 133, 203, 213, 216, 225,
79, 145, 168, 199 135-137, 144, 147-150, 244-245, 247, 253-255,
binary: 125, 192, 155, 166, 171, 174, 257, 260, 262, 264, 271
218, 225, 233 182-183, 193, 195, 199, compilers: 4, 7, 10,
blueprint: 88, 90, 146 202, 206, 225, 239-244, 68, 105, 245
boolean: 11, 195, 293 246-250, 253-254, compiles: 6, 143, 151
bounds: 38, 156, 298 257-258, 262-264, complete: 53, 91, 97,
braces: 9, 22, 25, 37, 39, 47 267, 270-271, 274, 278, 191, 213, 286
brackets: 25, 86, 288, 309-310, 312 complex: 2, 26, 40, 66,
135-136, 147, 159, 191 classname: 86-87, 90, 93 70, 79, 81, 84, 88, 144,
built-in: 1, 11-12, 40, 62, collection: 88, 206, 219 160, 173, 175, 216, 279
71, 108, 130, 268 column: 38, 156, 298-299 complexity: 85,
columns: 156, 162, 247, 192, 198, 270
C 283, 298-299
combine: 84, 179, 278
compose: 90, 239, 263
composed: 9, 44, 46-47,
calculate: 59, 63, 287 combined: 130, 57, 84, 200, 210, 215, 267
caller: 44, 46, 57-58, 61, 144, 178, 182 compute: 80, 85, 157,
64-68, 73-75, 160, common: 4, 7, 13, 25, 184, 234, 258, 300
168, 211, 261, 274 44, 64, 70-71, 86, 92, computed: 55-56, 81,
capacity: 186-187, 110, 134, 150, 156-157, 145, 198-199, 201, 258
203-204, 242-243 173-174, 178, 182, 202, concept: 15, 36, 43-45,
categories: 11-12, 216, 224, 228, 237, 268 51, 74, 79, 81, 84, 88,
218, 221, 237 comparator: 148-149, 95, 101-102, 114, 130,
certain: 29, 45, 153, 182 159, 176, 192-194, 197 179, 206, 237, 240,
chapter: 136, 141, 143, 146, compare: 127, 138, 146, 176, 254, 262, 271, 283
166, 215, 258, 260 193, 197, 215, 293, 305 concrete: 139, 262, 273
character: 11, 13, 75, compared: 71, 192, condition: 25-30,
153, 156, 203-204, 198, 204, 215 32-33, 35, 78, 126,
249, 309-310 comparison: 128, 145, 208, 251, 282, 284
characters: 11, 60, 142, 176, 192, 204, 293 conditions: 26, 28, 78, 123
151-152, 202-204, compile: 3, 7-10, 19, 37, connected: 90,
237, 249, 309 47-48, 50, 77, 116, 136, 153, 275-276
connection: 151-153, 268, contrary: 50, 52, 62, 119 creation: 116, 176, 184, 206
275-278, 295, 314-315 control: 1-2, 24-26, 28, 40, current: 30, 33, 56,
consider: 143, 161, 48, 124, 130, 272, 278 59-60, 63, 77-78, 89,
176, 240, 263 convenient: 61, 75, 96, 117, 141, 147, 151,
considered: 38, 105, 86, 96, 237, 273 153-154, 186, 208,
186-187, 234 convention: 5, 7, 88 212-214, 218, 227,
consists: 2, 6, 29, 34 converted: 79, 203, 297 242-243, 296-297, 307
console: 31, 46, 48, 62-63 converts: 4, 103, 297 customers: 223, 234, 264
constant: 18-19, 21, coordinate: 85, 88-89,
28, 37, 55, 80, 139,
198, 203, 218
107-108, 112, 127, 289
copies: 117, 119, 145,
D
const-ness: 74, 185, 217-218, 253 database: 264,
95, 164, 255 correct: 6, 38, 66, 102, 268, 277, 314
construct: 29, 118-119, 154, 158, 211, 244, 266, decide: 67, 74-75, 85,
121, 123, 203, 264, 275 269-270, 286, 307 166, 169, 198-199
contain: 4, 13, 50, 90, counts: 48, 69, 221 decided: 57, 182, 268
101, 170-172, 191, course: 87, 166, 172, 281 decides: 163, 254,
202, 207, 210, 253 create: 1-2, 4, 8, 10, 36, 43, 267, 270-272
container: 61, 140, 47, 49-51, 56, 58-59, declare: 11, 13, 15, 18-19,
142-143, 145, 150-151, 61-63, 76, 78, 81, 84-85, 29, 40, 43, 49, 53, 58,
177-179, 181-184, 88-90, 92, 101-103, 108, 66, 76, 79, 81, 83-84,
187-188, 190, 192-193, 111-112, 115-116, 130, 137, 89, 94, 99, 111, 113,
199-200, 215-216, 144-145, 152-153, 156, 115-117, 142, 144-145,
219-224, 228, 232 163, 170, 175-176, 179, 175, 191, 218, 223, 253,
containers: 112, 136, 190, 196, 201, 206-207, 255, 258, 264, 292,
143, 146, 150, 173, 177, 209-210, 214, 225, 237, 295-296, 302-303, 305
179, 181-182, 190-194, 239-240, 242, 248-249, declared: 13-14, 19, 22-23,
197-199, 202, 206, 222, 254, 256, 258, 260, 30, 36-37, 45-46,
224, 236-237, 304 264, 266-270, 273, 50, 53, 57, 67, 76-77,
contains: 4, 9, 77, 90, 98, 275-278, 282-287, 289, 80, 87, 90, 94-95,
116, 119, 136, 150-151, 291, 297-298, 301, 303, 105, 111-113, 117, 120,
176, 183-185, 187, 192, 305-309, 311-315 136-137, 140, 144, 147,
207-209, 212-213, 292 created: 19, 56, 59-60, 149, 151, 155, 158, 161,
content: 4-6, 16, 37, 53, 62, 67, 70, 78, 100, 102, 165, 171, 173, 175, 179,
121, 212, 230-231 146-147, 162, 184-185, 255, 258, 271, 304
context: 45, 96, 270 190, 225-226, 237, 252, declares: 50, 77, 113,
contiguous: 36, 258, 263, 266-269, 149, 178, 255, 261
183-184, 186, 202 273-274, 289-292, 295 deduce: 14, 136,
continue: 25, 29, creates: 5-6, 53, 60, 67, 141, 148, 161
32-33, 262 102-103, 108, 116, 139, deduced: 145,
continues: 29, 32, 34 152, 173, 225, 242, 252, 160-161, 165, 172
contract: 73, 222, 261 272-273, 291, 314
deduction: 14, 30, delimited: 9, 22, 47 difficult: 51, 61, 84
158, 160, 165, 179 demystify: 3, 27, dimensions: 38, 156, 298
default: 28-29, 37, 54-55, 45, 164, 205 dispatch: 251, 253-256,
60, 62, 72, 75-77, 81, depend: 147, 179, 197 258, 260, 268, 278
87-88, 102, 105, 108, dependent: 150-151, distance: 80-81, 84-85,
158-159, 161-163, 172, 175, 178-179 88-89, 112, 287
176, 179, 192, 196, depends: 88, 136, 178, 216 document: 73, 102, 143
201, 209, 221, 226, derive: 161, 241, 246, 254, donald: 196, 236, 305
236, 244-245, 253, 261-263, 266, 268, 311 double: 11, 87, 92, 112,
263-264, 268, 272, 274, derived: 87, 151, 161, 115, 134, 161, 163,
278, 300-301, 312 240-247, 249, 251-258, 172-173, 205
defaults: 12, 75, 244 261, 264, 268, 270, duration: 53, 80, 91
define: 1, 6-7, 9, 11, 17-18, 274, 278, 309 dynamic: 237, 239,
35, 40, 47-48, 50, 55, 78, describe: 85, 121, 136, 309 251-254, 256, 258, 260,
81, 83-84, 86, 88, 90, described: 38, 40, 266-273, 276, 278-279
93, 97-99, 108, 111, 124, 170, 179, 213
128-129, 135-136, 147,
150, 179, 246, 259-260,
destroy: 106, 109, 270, 272
destroyed: 54-56, 67,
E
262-263, 268, 278, 282, 106, 110, 122, 271, earlier: 36, 61, 65-66, 70,
288, 290, 294, 299, 311 273-275, 314 102, 128, 137, 139, 170,
defined: 4, 6-7, 11, 15, destructor: 106-109, 213, 222, 224, 240, 250,
28, 48, 50, 54-58, 111, 121-122, 245, 252, 268, 272-274, 293
62, 77, 79-80, 87, 258, 263, 267-268, easier: 44, 68, 75, 85, 130,
89-91, 98, 108, 116, 271-272, 274, 290 144, 158, 162-163, 174,
119, 124, 126-129, 145, detail: 10, 13-14, 51, 179, 266, 270, 274, 300
184, 188-190, 193, 58, 63, 101, 112, 182, effective: 12, 134, 259, 279
195, 197, 200-201, 217, 244, 266 element: 30, 37, 39-40,
206-207, 219, 225-226, details: 45, 85, 100, 56, 71-72, 143, 148-151,
261, 264, 277-278, 137, 177, 215, 279 156, 177-178, 182,
292-294, 299, 304 determine: 1, 13, 37, 207 184-187, 189-190,
defines: 6, 11, 76, 88, determined: 37, 45, 145, 192-193, 195, 198-201,
90, 126, 129, 202, 192-193, 198, 201 206, 210, 215, 217-219,
258, 261-262, 266 determines: 240, 270, 272 222, 224, 230-231, 235,
definition: 1, 9, 13, 39, 45, developer: 2, 248-249, 270 268, 298, 304, 306
47-50, 76-77, 90-91, 94, developers: 102, 134, embedded: 12, 244,
103, 117, 127, 136-137, 170, 240, 266 252-253, 271
139, 144-145, 147, 157, diagram: 3, 31, 38, enable: 2, 51, 66, 108, 121,
261, 263, 293, 300 187, 189, 217 245, 253-254, 257
delete: 123-124, 264, difference: 4, 40, 50, enables: 44, 86, 250
267-275, 291 69, 72, 88, 119, 121, enclosed: 25, 29, 135-136
deleted: 272, 275, 291 135, 149, 155, 164, encounters: 4, 46,
deletion: 111, 188, 290 190, 201, 258, 287 125-126, 136
enemies: 80, 249, 287 execute: 1, 3, 24-26, 28, extend: 108, 129, 294, 300
enough: 11, 156, 185, 50, 58, 101, 116, 155, 164, extended: 14, 36, 206, 290
187, 253, 267-269, 169-170, 174, 182, 225, extract: 206, 235, 296
276, 298, 305 253-254, 266, 302-303
ensure: 102, 108,
168, 268, 302
executed: 2-3, 7, 10,
24-30, 34, 44, 46-47,
F
entire: 25, 110, 134, 270 53, 67, 102, 104, 147, factory: 140, 276, 313-314
equivalent: 13, 37, 88, 96, 155, 162, 167, 182, 226, faster: 60, 155, 168, 198
126, 146, 167, 175, 206 253, 255-258, 269 feature: 14, 75-76, 79-80,
errors: 33, 38, 49, 64, 136, execution: 7, 25, 29, 32-34, 104, 136, 144-145, 154
143, 158, 179, 240, 268 46, 48, 52-54, 59, 65, features: 64, 81, 134, 144,
evaluate: 19, 191, 208, 304 67, 207, 210, 252, 267 158, 175, 179, 182, 209,
evaluates: 25-26, exercise: 10, 35-36, 49, 237, 239, 251, 253, 274
30, 32-33, 35 55-56, 59, 62, 92, fields: 30, 86, 90, 92-93,
example: 4, 11, 15, 17, 19-21, 97, 107, 115, 137, 203, 101, 104, 130, 142, 147,
23, 26-27, 30-34, 37, 211-212, 218, 220-221, 149-150, 176, 241, 253,
45-47, 54-55, 57-58, 223, 234, 242, 248, 254 259, 263, 272, 309
60, 65-66, 68, 70-71, exists: 6, 29, 47, 61, 134, figure: 3, 6-9, 14, 22, 31,
73-77, 79-80, 84-85, 162, 195-196, 275-276 38, 53, 85, 88, 125,
88-89, 91, 96, 98-106, expand: 172, 174, 303 160, 165, 183-186,
112-114, 118-121, 124, expanded: 4, 172-173, 303 188-190, 192, 194,
126, 128-129, 135, 137, expect: 37, 51, 73-74, 101, 197-200, 216-217,
140, 144-150, 155, 125-126, 161, 211, 263 219, 221, 228-229,
159-161, 163, 165, expected: 66, 74, 79, 102, 231-233, 241, 244
172-173, 176, 178, 187, 105, 124, 258-259, 263 finished: 67, 70, 275
193, 205, 208-209, 211, expects: 45, 65, 246 floats: 79-80, 84-85, 276
215, 219, 222, 225, 227, explain: 1, 43, 181 follow: 26, 67, 99, 152-153,
240, 246-247, 251-252, explained: 5, 30, 156-157, 168, 179, 190,
256-257, 261-262, 266, 69, 179, 278 223, 257, 263, 300
268, 270-273, 275-276, explicit: 96, 103-104, followed: 16, 26, 33, 46-47,
289-290, 293, 301 129, 167, 203, 209, 57, 65, 76-77, 87-88,
examples: 65-66, 70, 85, 211, 257, 291 128, 135-136, 144-146,
95, 125-126, 158, 164, explore: 10, 24, 29, 31, 155, 158, 163, 172, 217,
170, 176, 213, 246 47, 51, 58, 84, 86, 88, 236, 241, 247, 267
except: 75, 95, 116, 134, 190 99, 136, 144, 209, follows: 3-5, 15, 17-21, 27,
exception: 33-34, 73, 88, 218, 247, 251, 254 29-30, 32-33, 46, 57, 71,
109, 184, 196, 207-209 explored: 81, 130, 237, 278 89-90, 93, 97, 99-100,
exceptions: 33-34, express: 20, 44, 80, 95, 175 107, 115, 126-128, 136,
109, 207 expression: 25, 28-29, 138, 148, 157, 160, 163,
executable: 2, 4, 6, 8, 35, 58, 65, 67-70, 170, 176, 178, 202, 204,
10, 40, 47, 49-50 74, 121, 126, 144, 211, 213, 219-220, 222,
225, 267-269, 274 234, 241, 243, 249-250,
255, 260, 265, 267, 211, 215-216, 219-221, header: 4-8, 29-30, 35,
273, 278, 282-283, 285, 225-226, 228, 240, 49, 55, 80-81, 90-92,
289-291, 294, 296, 259-262, 266, 274, 97, 107, 115, 137, 152, 162,
299-302, 304, 315 277-278, 284-285, 295, 184, 188-193, 195-196,
forget: 25, 84, 86, 299, 306, 308, 314 200-201, 203, 206-207,
245, 257, 267 functor: 128-129, 211-212, 218-221, 223,
former: 15, 29, 96, 109, 192, 197, 225 242, 248, 254, 282,
116, 119-120, 184, 186 functors: 83-84, 284-286, 304-306
forward: 137, 145, 165, 128-129, 294 headers: 4, 7, 9, 235, 295
167-170, 175, 179, height: 99-100,
190, 215, 217-219,
236, 302-303
G 104, 115, 207
hierarchy: 217, 242,
friend: 83-84, 113-116, 292 general: 16, 58, 160, 246 247, 258
function: 1, 7, 9-10, 40, generate: 56, 123, high-level: 2, 84, 175, 179
44-51, 53-76, 78-81, 136, 139, 141 however: 2, 20, 66, 68,
86, 89, 91-101, 104, generates: 47, 120, 137, 179 90, 94, 124, 240
106-107, 109-111, 113, 115, generic: 14, 26, 63-64, 133,
121, 127-129, 134-142,
144-146, 153, 157-158,
135-136, 147, 150, 154,
163, 179, 185, 218, 295
I
161-174, 177-179, 182, getters: 98-99, 288, 290 identical: 49, 88,
184-187, 190, 192-193, global: 22-24, 43, 95, 134, 149
195, 197-198, 200-201, 50-51, 53, 55-56, identifier: 36, 57, 75-78,
203-209, 211-213, 77, 90-91, 286 151, 155, 158, 165, 169
215, 218, 220, 222, greater: 11-12, 26, 72, identify: 17, 57, 108,
225-227, 235-237, 243, 134, 138, 159, 194 133, 155, 264, 289
248, 253, 255-258, guarantee: 49, 100, 275 if-else: 25-26, 28, 78
260, 262, 265-275, guaranteed: 11-12, imagine: 75, 77, 79, 90, 101,
277-278, 282-287, 21, 51, 72, 218 112, 128, 176, 207, 213
294-295, 297, 299-303, guarantees: 32, 218, 273 implement: 36, 44, 59,
305-306, 308, 312-315 guidelines: 71, 263, 278 80-81, 83, 86, 101,
functional: 162, 108, 130, 133-134,
192-193, 300
functions: 4-5, 9, 11, 24,
H 149, 159, 176, 239,
250, 254, 259-260,
40, 43-44, 50, 58, 60, handle: 34, 109-110, 130, 262, 267, 307, 310
65-66, 70, 72, 75-76, 145, 213, 276, 279 implicit: 66, 103, 117,
78-81, 83-84, 86-87, happen: 100, 179, 119-120, 123-124
93-95, 97-99, 101-103, 207, 213, 276 import: 35, 187, 282
105, 108, 111, 113-114, happens: 2, 54, 67-69, important: 7, 10, 15, 18,
124, 130, 133-137, 139, 72, 111, 116, 136, 161, 26, 29, 38, 45, 52, 67,
144-148, 155, 160, 163, 166, 171, 200, 207, 70, 73, 76, 90, 95, 101,
169-171, 174, 182, 184, 222, 253, 267, 276 112, 143, 168, 182, 199,
190, 193, 200-208, hardware: 2, 7, 251 222-224, 237, 246, 252,
255, 258, 263, 268, 274 256, 258, 264, 266, 271, 236, 248, 255, 294
include: 2, 4-5, 7-9, 17, 282, 295-296, 298, 301, invoked: 47, 72, 79,
19-21, 23-24, 27, 31, 303, 305-306, 308 93, 102, 225, 227
34-35, 38, 46, 48, 50, instance: 88-93, 101, 109, iostream: 2, 4, 17, 19-21,
52, 55-56, 63, 92, 97, 111-112, 116, 128, 136, 23-24, 27, 31, 34-35,
107, 137, 143, 187, 191, 139, 146-148, 152-153, 38, 46, 48, 50, 52, 55,
193, 195-196, 203-205, 162, 191, 207, 267, 269, 63, 187, 193, 195-196,
207-209, 211-212, 215, 272, 275-276, 291, 203-205, 207-209,
218, 220-228, 230-235, 295, 300-301, 313 211-212, 215, 218,
242, 248, 282, 284-286, instances: 19, 88-90, 220-222, 225-228,
289-290, 292-293, 92, 130, 176, 262, 230-235, 242, 248, 282,
295-296, 298, 300, 268, 270, 276 284-286, 289-290,
304-306, 313-314 instead: 5, 21, 26, 32, 292-293, 295-296, 313
included: 5, 29, 49, 80, 44, 51, 53, 55, 68-70, isloggedin: 168-170,
90, 184, 215, 281 73-75, 84, 89-90, 110, 174, 302-303
increase: 29-30, 122, 155, 162-163, 177, iterate: 30, 40, 112, 182,
36, 191, 282 205, 210, 244-245, 253, 190, 215, 218-220,
increment: 35, 79, 257, 261, 266, 301 223, 229, 282-283
182, 217-219 integer: 11-12, 18, 40, iteration: 29-30, 32-33, 36
indeed: 12, 14, 21, 274 57, 65-67, 69, 73, 79, iterator: 112, 181, 215-224,
indicate: 16, 21, 26 141, 144, 191, 208, 230-231, 235, 237
inherits: 240, 242-243, 211, 215, 283-286 iterators: 112, 181-182,
246, 265, 309, 313 integers: 12, 40, 54, 215-224, 230, 237
initial: 17, 37, 157, 60, 65, 72, 79-80, itself: 21, 51, 64, 98,
163, 190, 299 84, 101, 111, 134, 146, 117, 121, 171
initialize: 13, 16-18, 29, 35, 221, 223, 225, 290
37, 40, 58, 90, 100, 102,
104-105, 108, 129-130,
intended: 73, 134, 253
interact: 84-86, 101, 149,
K
156, 170, 191, 244-245, 171, 211, 246, 266-267 keyword: 12, 14, 18-19, 21,
266, 268, 271, 278, interacts: 51, 85, 150 25-26, 29-30, 32-34,
282, 290, 294, 298 interested: 45, 176, 190 65-66, 76-77, 88, 90,
insert: 104, 169, 185, 188, interface: 86, 98, 112, 182, 94-97, 113, 135, 144, 151,
190, 193, 195, 197, 200, 185, 190, 200-201, 203, 155, 175, 178, 253-255,
215, 220-223, 235, 305 215, 244, 254, 260-264, 257-258, 263, 267, 278
inside: 6, 31, 34, 46, 51, 267, 276, 312-314 keywords: 14, 27, 267
57-58, 64-66, 71, 76-78, interfaces: 239, 260,
85-87, 89-90, 93-94,
97, 104-105, 108, 111-112,
263-264, 266, 270, 278
invalid: 16, 18, 64, 69, 77,
L
144, 151, 156, 161-162, 209, 222, 227, 274 lambda: 128, 181,
178-179, 186, 196-198, invoke: 44, 89, 93, 100, 225-227, 235-237
202, 206, 211-212, 223, 112, 115, 128-129, language: 2, 4, 7, 10,
226, 228, 244, 252-253, 163-164, 170, 227, 12-13, 38, 40, 44-45,
102, 124, 203 logger: 4-6, 129, 239, 244, 252-253, 258,
languages: 11, 44-45, 109 240, 266-267, 266-271, 273, 276, 279,
latitude: 84-85, 88-89, 91, 269-270, 272-273 285, 290, 305, 313
98-99, 108, 288-290 longer: 53, 70-71, mentioned: 12, 57, 68, 70,
latter: 29, 109, 120, 222-223, 267, 274 87, 173, 215, 250, 273
184, 186 longitude: 84-85, 88-89, message: 50, 63, 78, 97,
learned: 40, 50, 88, 90-91, 91, 98-99, 108, 288-290 142, 248, 257, 284, 307
95, 99, 134, 144, 146, looked: 190, 199, method: 62, 89, 93, 105,
154, 163, 166, 191, 237, 236-237, 261, 272 112, 115-116, 125-126,
240, 254, 266, 270 lookup: 192-193, 198, 236 141-143, 145, 148-152,
length: 36-37, 39, 106, 154, 156-157, 162, 166,
124, 157, 204, 206,
230-231, 300
M 209, 214, 221, 241, 243,
246-247, 249-250,
lesson: 1, 5, 9, 13-14, 17, machine: 4, 11, 45 253-258, 260-263, 271,
19, 24, 40, 43, 50-51, maintain: 51, 168, 171 274, 276, 292, 295-298,
61, 63-64, 68, 72, 81, manage: 109, 194, 206, 300-301, 306-311, 313
83-84, 87, 95, 100, 102, 239, 258, 270, 279, 313 methods: 86, 90, 93,
105, 112, 128, 130, 133, managed: 130, 167, 291 97, 99, 101, 114, 116,
140, 147, 162, 179, 181, management: 76, 214, 306 121, 130, 147-149, 208,
183, 191, 195, 199, 204, manager: 258, 214, 219, 241-242, 247,
216, 221-222, 224-225, 260, 311-312 250-251, 253-254,
236-237, 239, 266, manages: 111-112, 213, 261-263, 266, 278, 288,
274, 278-279, 282, 284, 276, 290, 314 292, 308, 310-311
288, 295, 304, 309 manipulate: 93, 202, 237 modern: 19, 266, 270, 279
library: 4, 62, 76-77, matrix: 156-157, modifier: 83, 87-88,
80, 100, 112, 134, 136, 162-163, 298-301 139, 241, 278
143, 146, 170, 174, mechanism: 58, 109, 203 modifiers: 12, 83, 130, 139
179, 181-182, 187, 199, member: 83-84, 86-87, 89, modify: 21, 44, 51, 58-61,
203, 211, 224, 270, 91-97, 99-100, 104-105, 64, 69, 72-75, 94,
273, 276, 286, 304 111, 113, 117, 119-122, 139, 161, 174, 196,
lifetime: 52-53, 59, 127, 129, 141, 149, 184, 226, 228-229, 305
67-70, 91, 109-110, 266, 186-187, 200, 204, 215, moreover: 94, 134, 184
270-272, 276, 314 218, 290, 294, 296, 309 multimap: 194-195,
limited: 28, 36, 38, 116 members: 83, 86-94, 206, 236
linear: 24, 218, 233 98-102, 104-106, 108, multiple: 5, 13, 15, 17,
linker: 47, 49, 90 111-114, 117, 119-121, 24-26, 29, 49, 54, 77, 80,
location: 3, 8, 13, 15, 21-22, 130, 193, 206, 241, 88-90, 92, 111, 121, 130,
38, 64, 80, 84, 127, 169, 263-264, 271, 288-290 133, 139, 153, 171-172,
176-177, 222, 252, 287 memory: 11-13, 15-17, 179, 195, 198, 210, 215,
locations: 36, 108, 21-22, 36-38, 52, 60-62, 227, 242, 246-248,
127, 173, 257 64, 90, 110-111, 121, 159, 262-263, 267-268, 275,
183-189, 191, 217, 222, 277, 279, 290, 296, 314
multiplier: 75, 162, 227, 301 objectives: 1, 43, 83, options: 7, 58, 65-66
mutable: 51, 92, 202 133, 181, 239, 281 original: 64, 112, 119, 139
objects: 13, 21, 52, 54, 68, otherwise: 25-26, 29,
N 70, 73, 75, 83-84, 88, 93,
106, 112, 127, 134, 143,
73, 78, 95, 208-209,
227, 244, 274, 298
namespace: 76-78, 205, 151-152, 159, 166, 179, output: 17, 19-24, 27, 31,
254, 285-286, 304 182, 192, 204, 212, 250, 34, 39, 47-50, 78, 93,
namespaces: 43, 266, 268-269, 271-272, 97-98, 107, 109, 115,
76-79, 81, 285 275, 279, 293, 295 120, 129, 138, 157, 163,
necessary: 14, 17, 19, 84, obtained: 14, 184, 215, 222 167, 188, 194, 204-209,
105, 121, 170, 257 offers: 24, 40, 75, 81, 211, 213, 218-220,
needed: 14-15, 38, 44, 134, 158, 273, 276 222-223, 225-227, 229,
159, 187, 213, 266 omitted: 30, 75, 244 231-234, 243, 249-250,
negative: 12, 228-229, 270 operand: 126, 162, 301 255, 260, 265, 278,
nested: 23, 31, 39-40, 76, operate: 80, 84-86, 90, 282-287, 291, 294,
83, 111-112, 175, 178, 283 109, 124, 215, 224, 236 296-297, 299-302, 304
non-const: 20-21, operation: 13, 24, 40, 46, outside: 22, 33-34, 50,
69, 95, 161 61, 64, 68, 74, 79, 100, 60, 87, 89-90, 94,
non-member: 98, 113, 127 126, 140, 157, 162-163, 113, 156, 241, 308
non-type: 154-155, 175, 182, 187, 216, 219, overcome: 61, 225, 270
158-159, 179 225, 233, 277, 301, 308 overload: 79-80, 83, 102,
normal: 33, 101, 166, operations: 17, 24, 44-46, 119, 127, 134, 149, 212,
201, 207, 219 84-86, 98-99, 109-110, 225, 256-257, 293
notice: 38, 104, 139, 276 134, 156, 162, 174, 177, overloaded: 43, 95,
numeric: 11-12, 205, 233 181-182, 184, 202, 218, 102-103, 125, 149, 205
numerical: 78, 124, 134 224, 228, 236, 277, 288, overloads: 128,
298, 303-304, 314 149, 171, 210
O operator: 15-16, 35, 89,
91-94, 119-121, 123-130,
override: 255-257,
260-262, 265,
object: 2, 4, 7-8, 15, 134, 149, 159, 162, 176, 278, 311, 313
17-22, 52-54, 59, 182, 184, 190-192, 196,
61-64, 67-71, 73-74, 88,
90, 94-97, 100-104,
201, 204, 209, 212,
215, 218, 224-225,
P
106, 108-110, 112, 114, 245, 247, 253, 264, parameter: 7, 46-47,
116-117, 119-121, 124, 268-269, 293-294, 57-59, 61, 63-65,
126-129, 139, 142-143, 299, 304, 308, 312 71-72, 74-75, 99-100,
152-153, 159, 166-167, operators: 83, 117, 123-126, 102-103, 111, 121,
174, 182, 192, 197, 212, 149, 176, 182, 204, 125-127, 129, 135-136,
225-227, 251-253, 255, 216-217, 245, 263-264 139-140, 145, 148,
258, 263, 267-268, option: 60, 71, 159 150, 153-155, 158-164,
270-275, 289-295 optional: 9, 12, 29-30, 39, 166-167, 169-174, 179,
202, 207-210, 213, 237 182, 192, 197, 200, 207,
288, 290, 293-294, pointers: 1, 15, 17, 19-21, 293, 298, 301, 303
296-297, 300, 302-303 40, 64, 70, 182, prints: 46, 49, 59-60, 62,
parameters: 7, 45-46, 252, 262, 267, 270, 71, 106, 115, 118, 120,
48-49, 57-58, 60, 272-274, 276, 279 129-130, 214, 235, 240,
62-63, 65, 72-73, 79, points: 15-16, 64, 80-81, 249-250, 292, 309-310
81, 99-100, 102-103, 127, 176, 189, 218-219, private: 78, 83, 87-88,
126, 134-136, 139-140, 270, 272, 274, 293 99, 101, 104-105, 108,
144-147, 149, 151, position: 36-37, 110-114, 116-117, 119-120,
154-156, 158, 160-162, 55-56, 108, 156, 182, 122, 125, 129-130, 241,
164, 167, 169-176, 179, 184-185, 190-191, 244-245, 249, 252,
182, 206, 225, 244, 193, 195, 197-198, 259, 275, 288-292,
255, 261, 267, 295, 215, 217-218, 220, 294, 297, 309-310
298, 300, 302-303 222-223, 229, 249-250, problem: 5, 51, 58, 73,
parent: 76-77, 255, 272-273 304-305, 309-310 142, 150, 169, 171,
particular: 19, 22, 182 positions: 38, 108-109, 111, 198, 263, 268, 276
passed: 59-64, 71, 117, 186, 198, 219, 289-290 problems: 5, 33, 70, 76,
125, 167, 235, 272 possible: 13, 15-18, 20, 23, 134, 155, 175, 240, 247,
pattern: 5, 171-173, 178, 26, 28, 51, 67, 74-75, 264, 266-267, 279
200, 217, 251, 254 103, 127, 134, 144, 161, process: 3-4, 6, 10,
perform: 6, 35, 40, 74, 167, 171, 187, 190, 205, 40, 45, 85, 201-202,
79, 86, 96, 99, 108, 210, 212, 227, 240, 251, 277, 306-307
127, 129, 137, 155, 160, 264, 270, 273, 277, 314 produce: 4, 6, 10, 26, 38
162, 168-170, 174, 182, powerful: 79, 81, 130, produces: 35, 45, 49
184, 202, 211-212, 218, 136, 174-175, 251, 270 profile: 264-265, 277, 313
220-221, 225, 242, 252, practice: 25, 51, 55, program: 2-4, 6-7, 9-10,
254, 259, 261-262, 264, 67, 75-76, 102, 109, 18, 22, 24-25, 29, 32-34,
277, 281, 301, 303, 308 149, 161, 262-263 38, 40, 44-49, 51-53,
performed: 13, 17, 45, preferred: 62, 166, 188 55, 57-59, 62-63, 65-67,
64, 182, 224, 281 present: 12, 29, 45, 69-70, 77, 80, 84, 87-88,
performs: 45, 61, 101, 172, 196, 207-210 91-92, 97, 100, 107-108,
117, 136, 170 prevent: 5, 18, 75, 111, 115, 133-134, 136,
person: 59, 284, 309 116, 121, 123-124 140, 153, 155, 159, 170,
placed: 21, 31, 36, 94 prevents: 29, 136, 267 203, 207, 210-214, 218,
places: 44, 95, 128 previous: 15-16, 19, 26-27, 220-222, 234, 240-242,
please: 63, 284, 286 35-38, 56, 60, 62-63, 248, 250-252, 254, 260,
pointed: 15-16, 21-22, 65, 67, 70-72, 84, 90, 266-268, 270, 272, 282,
70, 96, 119, 267, 275 95, 97, 100-101, 103, 284, 286, 296-297
pointer: 15-18, 20-22, 113-115, 126-127, 134, programmer: 11-12,
53, 58, 64, 70, 73, 96, 144, 146, 156, 158, 162, 15, 24, 30, 33, 44,
111, 119, 146, 251-254, 174, 178-179, 190, 199, 58, 84-85, 87, 108,
267-270, 272-278 216, 218, 222, 250, 110, 174, 182, 202
260-263, 268, 270, 273,
programs: 1-2, 51-52, 81,
84, 92, 130, 203, 237,
Q regardless: 26, 32, 72,
163-164, 168, 258
239, 266, 270, 276, 279 qualifier: 18, 40, 51, 72, 255 registered: 176, 202,
properties: 45, 61, 207, 213, 234
93, 101, 239
property: 45, 101, 170
R regular: 144, 147, 149,
155, 261, 272
protected: 83, 87, 105, radius: 80, 98, 286-287 related: 15, 112, 116
113-114, 241-243, random: 100, 186, release: 111, 122,
252, 264, 312 190, 218, 231 187, 269, 273
provide: 24, 44, 57-58, rather: 182, 201, 220 remain: 121, 222, 267,
65, 75, 79, 84, 86, reaches: 53-54, 275-276, 315
90, 110, 112, 116, 145, 65-68, 110, 223 remains: 2, 17-18, 71,
154, 158-159, 161-162, reason: 7, 14, 18, 35, 60, 275, 277, 301
171, 176, 190, 193, 197, 67, 69, 72-74, 141-142, remember: 15, 55, 61, 64,
199-200, 202, 206, 145, 150-151, 159, 81, 84, 127, 149, 178, 222,
210, 215, 217-218, 224, 161, 182, 190, 252, 235, 254, 274, 308, 315
236-237, 241, 261, 263, 255, 258, 263, 285 remove: 73-74, 169-170,
274, 284, 296, 298, 313 reasons: 18, 51, 64, 189-190, 200-201, 204,
provided: 14, 25, 37, 39, 112, 161, 175 230, 235, 237, 275, 306
44, 59-62, 64, 73, rectangle: 99-100, removed: 159, 169,
79-80, 109-110, 123, 102-104, 106, 124, 246 184-185, 188-189, 200,
127, 136, 145, 152, 154, reference: 17, 19-21, 53, 230-231, 275, 304
160, 162, 165-166, 171, 58, 61-65, 67-74, 79, repeat: 29, 44, 90, 307
174, 176, 182, 190-191, 95, 117, 119, 121, 139, replace: 14, 246, 303-304
193-194, 196, 199, 201, 145, 156-158, 161, replaced: 4, 136, 274
204, 211, 216, 218, 224, 163-167, 179, 226-227, represent: 11-12, 33,
234, 267, 278, 286, 235, 251-255, 266, 44-45, 75, 84-85,
293, 295, 301, 303 272, 274, 278, 284-285, 108-109, 124, 137,
provides: 25, 63, 73, 75, 298, 300, 302 156, 203, 208, 210,
84, 104, 112, 128, 150, referenced: 20, 215, 226 213-215, 222, 270, 272
158, 167, 182, 195, 205, references: 1, 15, 17, 19-21, represents: 64, 96, 101,
208, 215-216, 219, 224, 40, 61, 63, 69-70, 72-74, 109, 127, 153, 155,
236, 260, 270, 313 136, 158, 164-165, 168, 177, 182, 207, 213,
public: 86-88, 91, 93-94, 167-168, 170, 206, 246, 250, 263, 275
97-99, 101-104, 106-109, 252, 262, 285, 303 requested: 111, 267, 298
112-113, 115-117, 119-121, referred: 4, 7, 13-14, 17, 33, require: 151, 184, 221, 262
126, 128-129, 142, 38, 65, 69, 86, 89, 94, required: 35, 92, 94,
150, 152-154, 156, 177, 98, 164, 182, 210, 215 97, 107, 115, 119, 123,
240-245, 248-250, refers: 13, 22, 52-54, 61, 134, 137, 162, 168, 170,
252, 254-255, 259-265, 89, 117, 147, 160, 252 188, 203, 210-211, 213,
275-276, 288-294, 296, ref-ness: 72, 164, 168, 221, 235, 248, 254,
298, 301, 307-313 170, 174, 255, 303 282, 285-286, 304
213-214, 225, 247, 250,
requires: 13, 16, 71, 86,
101, 140, 162, 171, 176,
S 258, 262-264, 266,
186, 188, 225, 262-263 search: 77, 193, 233 268, 270, 272-274,
reserve: 187, 191, 305 second: 4-5, 38, 55, 282, 285, 288, 294,
resources: 121, 130, 268 78, 95, 102-103, 105, 296-297, 302-303, 310
respected: 102, 143, 272 118, 122-123, 126, 144, showed: 102, 130, 179,
respective: 8, 253, 277 155-156, 169, 177, 274, 276, 278
result: 5, 16, 23, 38, 44, 195-197, 204, 206, signature: 46, 48, 57,
69-70, 128, 157, 162-163, 218, 235, 243, 283, 73-74, 76, 119, 144, 168,
174, 193, 195, 211, 252, 285, 296, 298, 305 172, 213, 255-257, 273
258, 268, 293, 300-301 section: 15, 29-30, 40, similar: 20, 29, 32, 38,
results: 6, 66, 246, 264 47, 57, 65, 67, 70, 90, 55, 69, 73, 90, 101-102,
retrieve: 15, 98, 136, 144, 146, 158, 174, 109, 112, 119, 128, 136,
168-169, 196, 207, 185-187, 199, 218, 250, 147-149, 161, 164, 170,
213, 264-265, 313 254, 260, 266, 270, 281 182, 185, 190, 192,
return: 2, 7, 9-11, 15, 40, selected: 34, 79, 206 195, 201, 206, 210,
43, 45-48, 52-53, semantic: 61-62, 68, 74 218-219, 225, 245-246,
55-56, 63-68, 70-72, 74, separate: 51, 84, 263 263-264, 275
79, 89, 91-92, 98-100, separated: 13, 75, simple: 2, 40, 66, 107,
106, 116, 124, 128-129, 77, 241, 299 129, 182, 225, 274
134-135, 137-140, 142, sequence: 24, 27, 30, 44, situation: 18, 25,
144-146, 152-153, 47, 55-56, 112, 182-183, 68, 174, 207
156-157, 159, 161-162, 187, 190-191, 200, snippet: 5, 16, 140
166, 168, 173, 198, 200, 217, 224-225, 236 software: 2, 44, 76, 170
209-210, 213, 225-230, sequences: 30, 143, solution: 13, 36, 40, 49,
234-236, 242-243, 202-203, 237 51, 63, 72, 74, 78, 80,
248-249, 255, 259-260, sequential: 29, 99, 109, 111, 117, 128,
265-266, 268-269, 181-182, 193, 236 130, 153-154, 157, 163,
273, 276, 284-286, several: 8, 18, 27, 44, 49, 170-171, 175, 191, 197,
288-289, 292-296, 65-66, 68, 79-80, 84, 202, 214, 235, 250, 260,
298-301, 307, 310-314 102, 137, 151, 172, 176-177, 264-265, 277-278, 300
returned: 46, 65-72, 182, 195, 203, 213, 218, sorted: 159, 192,
74, 143, 145, 177, 237, 264, 275, 314 194, 197, 236
223, 267-269 shared: 90, 149, 270, source: 2, 4, 7, 10, 14, 40,
reverse: 31, 106, 201-202, 275-279, 314-315 70, 121, 153, 221, 252
219-221, 258 should: 6-7, 17, 26, 40, 45, special: 6, 100, 117, 135,
runtime: 33, 36, 38, 155, 53, 60, 72-75, 77-78, 147, 156, 178, 193,
179, 251-252, 268, 276 84, 88, 99, 101, 105, 203, 225, 253
108, 122-124, 129, 137, specific: 5, 15, 22, 35,
143, 150, 154, 156, 159, 37, 45, 60, 75, 77, 85,
162, 166-168, 175, 186, 90, 102, 116, 134, 139,
192-193, 197-199, 207, 141, 147, 149-150, 153,
168, 175, 182, 190, 198,
206, 215, 232, 262
stored: 11, 15-17, 21, 56,
90, 99, 101, 156, 162,
T
specifier: 14, 83, 87, 182-183, 185, 188, 191, talked: 40, 170, 260, 278
99, 103, 108, 113, 198, 210-212, 217, 223, template: 128, 130, 133,
243-244, 288-289 226-228, 288, 295, 301 135-156, 158-163,
specifies: 45, 90, 103, stores: 11, 84, 153-154, 165-167, 169-179,
150-151, 241, 254, 263 185, 214, 235, 259 182, 200, 206-209,
specify: 8, 12, 14-15, 26, 49, stream: 63, 98, 224, 250, 273, 275,
57, 75, 77, 87, 102, 113, 109-110, 217, 221 295-300, 302-303
124, 136, 141, 145, 148, streams: 4, 109, 221 templated: 133, 135-136,
158-159, 175-176, 182, struct: 88-89, 96, 99-101, 144, 148-149, 163, 168
184, 192, 197, 200-201, 106, 112, 118, 123, templates: 14, 63-64,
206, 215, 225, 241, 254, 137-138, 142, 153, 159, 130, 133-137, 139-141,
257, 261, 268, 272 164, 168, 171-173, 201, 143-146, 149-150,
square: 81, 92, 212-214, 234, 246-247, 154, 158, 160-161, 163,
102-104, 246 249, 251-252, 256, 166, 170, 174-175, 179,
standard: 4, 7, 12, 14, 264, 309, 312, 314 240, 250, 261, 295
49-50, 62, 76, 98, structs: 88, 105, temporary: 30, 63, 67,
100, 112, 136, 141, 143, 137-138, 244 69-70, 73, 121, 161
146, 170, 174, 179, structure: 6, 11, 15, 26, 36, testlogger: 266, 269, 273
181-182, 202-203, 40, 44, 86, 176, 183-184, thanks: 80, 85, 179,
211, 221, 224, 237, 257, 188-189, 192, 200, 202 213, 216, 251, 274
270, 273, 276, 304 structures: 60, 176, 182 therefore: 127, 141, 151
started: 1, 40, 50, 81, studio: 3, 7, 10 though: 32, 61, 73,
130, 183, 216, 282 subsequent: 6, 25, 158 106, 110, 219
starts: 4, 38, 46, 48, 53, summary: 40, 81, 130, through: 18, 26, 73,
76, 135, 144, 155, 191 179, 236, 241, 278 98-99, 109, 112, 182,
statement: 9, 13-15, 22, support: 12, 25, 68, 80, 190, 193, 200, 217, 220,
25-30, 32-35, 37, 47, 87, 140, 153, 166, 168, 242, 282-283, 288
50, 54, 56, 66-69, 208, 177, 182, 212, 236, 296 throughout: 6,
248, 282-283, 286-287 supported: 7, 155, 160, 174 80, 170, 260
statements: 1, 24-26, supports: 11, 19, 66, together: 4, 7-9, 44, 49,
29, 32-34, 40, 47, 50, 153, 246, 251, 253 51, 76-77, 81, 84, 86,
58, 211, 225, 291 supposed: 45, 72, 99, 288 102, 244, 262, 276
static: 37, 83, 90-93, syntax: 13-16, 18-21, 26, transform: 139, 230, 235
111-112, 130, 149-153, 29-30, 32-33, 37-38, turnon: 93-94, 240-241,
183, 251-255, 295 40, 46, 75, 87, 90, 253-255, 261-262
status: 7, 9, 170, 214 93, 128, 144, 146, 148, typename: 135, 138-140,
storage: 13, 90, 236, 151, 158, 179, 225 145-156, 158-161,
265-267, 276, 313-314 system: 3, 7, 10, 100, 165-167, 169-179,
108, 207, 214, 258, 295-300, 302-303
266-267, 289, 306 typical: 150, 213, 240, 254
U variables: 1, 11, 13-15,
22-24, 43, 50-57, 59-60,
unique: 2, 6, 192, 270, 74, 78, 81, 84-86, 89-92,
272-276, 279, 313-314 100, 104-105, 145, 206,
unless: 51, 60-61, 225-227, 237, 267, 271
74, 77, 188 variant: 30, 202, 208,
unlike: 17, 155, 188, 190 210-213, 237, 306-308
unordered: 197-199, various: 44, 72, 213,
206, 236 218, 237, 250, 310
unsigned: 12, 35-36, vector: 143, 150, 157,
155, 282 162-163, 168-170,
update: 85, 176, 189, 191, 173, 184-188, 190-191,
222, 257, 273, 277, 304 215-216, 218, 220,
useful: 24, 81, 102, 112, 222-223, 228-236, 275,
128, 144-146, 155, 177, 300-302, 304-305
179, 203, 206, 210, 270 vectors: 79, 143, 185-186,
userid: 168, 264-265, 190, 202, 204
312-314 vehicle: 240-243, 246,
username: 153, 196, 253-255, 261-264
201, 234, 305-306 version: 21, 81, 108, 236
utilize: 1, 43, 83, 233 versions: 95, 160, 211
virtual: 105, 253-264,
V 268, 278, 311-312
visibility: 97, 241,
values: 11-12, 24, 35, 37, 40, 247, 252, 278
43, 51, 55, 57-58, 63, 65, visible: 22, 64, 137, 247
67, 69-70, 72, 77, 81, 84, visual: 3, 7, 10, 287
90, 99-101, 104, 108, 111,
151, 155, 158, 168, 170,
176, 182, 184, 190, 195,
W
205-206, 210, 212-213, website: 168, 201, 207
220, 231-232, 251-252,
283, 287-290, 298, 304
variable: 1, 6, 11, 13-24,
29-30, 35-37, 40,
50-56, 58, 60-62,
68-69, 71, 74-78,
86, 88, 90-91, 100,
125-126, 150-151,
203, 206, 226-227,
252-255, 266-267,
282, 284, 286-287

You might also like