Lecture 19
Lecture 19
Practical Course
Organization
2
Organization
Course Goals
3
Organization
Formal Prerequisites
4
Organization
Practical Prerequisites
Practical prerequisites
• No previous experience with C or C++ required
• Familiarity with another general-purpose programming language
Operating System
• Working Linux operating system (e.g. Ubuntu)
• Basic experience with Linux (in particular with shell)
• You are free to use your favorite OS, we only support Linux
5
Organization
6
Organization
Assignments
7
Organization
Grading
Grading system
• Quizzes: Varying number of points
• Weekly assignments: Varying number of points depending on workload
• Final project
8
Organization
Literature
Primary
• C++ Reference Documentation. (https://en.cppreference.com/)
• Lippman, 2013. C++ Primer (5th edition). Only covers C++11.
• Stroustrup, 2013. The C++ Programming Language (4th edition). Only
covers C++11.
• Meyers, 2015. Effective Modern C++. 42 specific ways to improve your use
of C++11 and C++14..
Supplementary
• Aho, Lam, Sethi & Ullman, 2007. Compilers. Principles, Techniques & Tools
(2nd edition).
• Tanenbaum, 2006. Structured Computer Organization (5th edition).
9
Organization
Contact
Important links
• Website: http://db.in.tum.de/teaching/ss19/c++praktikum
• E-Mail: freitagm@in.tum.de, sichert@in.tum.de
• GitLab: https://gitlab.db.in.tum.de/cpplab19
• Mattermost: https://mattermost.db.in.tum.de/cpplab19
10
Introduction
Introduction
11
Introduction
What is C++?
Key characteristics
• Compiled language
• Statically typed language
• Facilities for low-level programming
12
Introduction
Initial development
• Bjarne Stroustrup at Bell Labs (since 1979)
• In large parts based on C
• Inspirations from Simula67 (classes) and Algol68 (operator overloading)
13
Introduction
Performance
• Flexible level of abstraction (very low-level to very high-level)
• High-performance even for user-defined types
• Direct mapping of hardware capabilities
• Zero-overhead rule: “What you don’t use, you don’t pay for.” (Bjarne
Stroustrup)
Flexibility
• Choose suitable programming paradigm
• Comprehensive ecosystem (tool chains & libraries)
• Scales easily to very large systems (with some discipline)
• Interoperability with other programming languages (especially C)
14
Background
Background
15
Background Central Processing Unit
16
Background Central Processing Unit
Control
Unit
Arithmetic
Logical Unit
(ALU)
Registers
Bus
17
Background Central Processing Unit
Components of a CPU
Control Unit
• Fetch instructions from memory and determine their type
• Orchestrate other components
Registers
• Small, high-speed memory with fixed size and function
• Temporary results and control information (one number / register)
• Program Counter (PC): Next instruction to be fetched
• Instruction Register (IR): Instruction currently being executed
18
Background Central Processing Unit
A+B
A Registers
B
ALU
19
Background Central Processing Unit
20
Background Central Processing Unit
Instruction Categories
Register-register instructions
• Fetch two operands from registers into ALU input registers
• Perform some computation on values
• Store result back into one of the registers
• Low latency, high throughput
Register-memory instructions
• Fetch memory words into registers
• Store registers into memory words
• Potentially incur high latency and stall the CPU
21
Background Central Processing Unit
Fetch-Decode-Execute Cycle
22
Background Central Processing Unit
23
Background Central Processing Unit
24
Background Central Processing Unit
Instruction-Level Parallelism
Instruction Prefetching
• Fetch instructions from memory in advance
• Hold prefetched instructions in buffer for fast access
• Breaks instruction execution into fetching and actual execution
Pipelining
• Divide instruction execution into many steps
• Each step handled in parallel by dedicated piece of hardware
• Central to modern CPUs
25
Background Central Processing Unit
Pipelining (1)
S1 S2 S3 S4 S5
Instruction Instruction Operand Instruction Write
Fetch Decode Fetch Execution Back
Frontend Backend
S1 1 2 3 4 5
S2 1 2 3 4
S3 1 2 3
S4 1 2
S5 1
Time 1 2 3 4 5
26
Background Central Processing Unit
Pipelining (2)
27
Background Central Processing Unit
Superscalar Architectures
Superscalar architectures
• S3 stage is typically much faster than S4
• Issue multiple instructions per clock cycle in a single pipeline
• Replicate (some) execution units in S4 to keep up with S3
28
Background Central Processing Unit
Multiprocessors
29
Background Primary Memory
Main Memory
30
Background Primary Memory
31
Background Primary Memory
Address 0000 48 65 6c 6c 6f 20 57 6f
Hexadecimal 0008 72 6c 64 21 20 49 20 6c
0010 69 6b 65 20 43 2b 2b 21
Address
00 01 02 03 04 05 06 07
0000 H e l l o W o
Address
ASCII 0008 r l d ! I l
0010 i k e C + + !
32
Background Primary Memory
33
Background Primary Memory
00 01 02 03 00 01 02 03
00 00 00 2a 00 2a 00 00
34
Background Primary Memory
35
Background Primary Memory
36
Background Primary Memory
Introduce small but fast cache between CPU and main memory
• CPU transparently keeps recently accessed data in cache (temporal locality)
• Memory is divided into blocks (cache lines)
• Whenever a memory cell is referenced, load the entire corresponding cache
line into the cache (spatial locality)
• Requires specialized eviction strategy
Intel CPUs
• 3 caches (L1, L2, L3) with increasing size and latency
• Caches are inclusive (i.e. L1 is replicated within L2, and L2 within L3)
• 64 byte cache lines
37
Background Primary Memory
CPU 1 CPU 2
Main
Memory
L1-I L1-D L1-I L1-D
Unified L2 Unified L2
Unified L3
38
Background Primary Memory
39
Introduction to the C++ Ecosystem
40
Introduction to the C++ Ecosystem Hello World
myprogram.cpp
#include <iostream>
int main(int argc, char** argv) {
std::cout << "Hello " << argv[1] << '!' << std::endl;
return 0;
}
41
Introduction to the C++ Ecosystem Compiler
• Programs that transform C++ files into executables are called compilers
• Popular compilers: gcc (GNU), clang (llvm)
• Minimal example to compile the hello world program with gcc:
42
Introduction to the C++ Ecosystem Compiler
Compiler Flags
43
Introduction to the C++ Ecosystem make
make
• C++ projects usually consist of many .cpp (implementation files) and .hpp
(header files) files
• Each implementation file needs to be compiled into an object file first, then
all object files must be linked
• Very repetitive to do this by hand
• When one .cpp file changes, only the corresponding object file should be
recompiled, not all
• When one .hpp file changes, only implementation files that use it should be
recompiled
• make is a program that can automate this
• Requires a Makefile
• GNU make manual:
https://www.gnu.org/software/make/manual/make.html
44
Introduction to the C++ Ecosystem make
Basic Makefile
• Makefiles consist of rules and contain variables
• Each rule has a target, prerequisites, and a recipe
• Recipes are only executed when the prerequisites are newer than the target or
when the target does not exist
• Note: The indentation in Makefiles must be exactly one tab character, no
spaces!
Makefile
CONTENT="test 123" # set the variable CONTENT
# rule and recipe to generate the target file foo
foo:
echo $(CONTENT) > foo
# $^ always contains all prerequisites ("foo baz" here)
# $< contains only the first prerequisite ("foo" here)
# $@ contains the target ("bar" here)
bar: foo baz
cat $^ > $@
45
Introduction to the C++ Ecosystem make
46
Introduction to the C++ Ecosystem make
Advanced Makefile
• Recipes are usually the same for most files
• Pattern rules can be used to reuse a recipe for multiple files
Makefile
CXX?=g++ # set CXX variable only if it's not set
CXXFLAGS+= -O3 -Wall -Wextra # append to CXXFLAGS
SOURCES=foo.cpp bar.cpp
%.o: %.cpp # pattern rule to make .o files out of .cpp files
$(CXX) $(CXXFLAGS) -c -o $@ $<
# use a substitution reference to get .o file names
myprogram: myprogram.o $(SOURCES:.cpp=.o)
$(CXX) $(CXXFLAGS) -o $@ $^
47
Introduction to the C++ Ecosystem CMake
CMake
48
Introduction to the C++ Ecosystem CMake
Basic CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(myprogram)
set(MYPROGRAM_FILES sayhello.cpp saybye.cpp)
add_executable(myprogram myprogram.cpp ${MYPROGRAM_FILES})
49
Introduction to the C++ Ecosystem CMake
CMake Commands
cmake_minimum_required(VERSION 3.10)
Require a specific cmake version.
project(myproject)
Define a C/C++ project with the name “myproject”, required for every project.
set(FOO a b c)
Set the variable ${FOO} to be equal to a b c.
add_executable(myprogram a.cpp b.cpp)
Define an executable to be built that consists of the source files a.cpp and b.cpp.
add_library(mylib a.cpp b.cpp)
Similar to add_executable() but build a library.
add_compile_options(-Wall -Wextra)
Add -Wall -Wextra to all invocations of the compiler.
target_link_library(myprogram mylib)
Link the executable or library myprogram with the library mylib.
50
Introduction to the C++ Ecosystem CMake
CMake Variables
CMake has many variables that influence how the executables and libraries are
built. They can be set in the CMakeLists.txt with set(), on the command line
with cmake -D FOO=bar, or with the program ccmake.
CMAKE_CXX_STANDARD=17
Set the C++ to standard to C++17, effectively adds -std=c++17 to the compiler
flags.
CMAKE_CXX_COMPILER=clang++
Set the C++ compiler to clang++.
CMAKE_BUILD_TYPE=Debug
Set the “build type” to Debug. Other possible values: Release,
RelWithDebInfo. This mainly affects the optimization compiler flags.
CMAKE_CXX_FLAGS(_DEBUG/_RELEASE)=-march=native
Add -march=native to all compiler invocations (or only for the Debug or
Release build types).
51
Introduction to the C++ Ecosystem CMake
52
Introduction to the C++ Ecosystem CMake
cmake_example_project
├── CMakeLists.txt
├── lib • This project contains the library
│ ├── CMakeLists.txt greetings and the executable
│ ├── saybye.cpp print_greetings
│ ├── saybye.hpp • The library consists of the files
│ ├── sayhello.cpp sayhello.cpp and saybye.cpp
│ └── sayhello.hpp • You can find this project in our
└── src
Gitlab
├── CMakeLists.txt
└── print_greetings.cpp
53
Introduction to the C++ Ecosystem Git
Git
54
Introduction to the C++ Ecosystem Git
Git Concepts
55
Introduction to the C++ Ecosystem Git
No commits yet
56
Introduction to the C++ Ecosystem Git
When working with a Git repository, changes can live in any of the following
places:
• In the working directory (when you edit a file)
• In the staging area (when you use git add)
• In a commit (after a git commit)
Once a change is in a commit and it is referenced by at least one branch or tag
you can always restore it even if you remove the file.
working directory staging area commit
git add
git commit
git checkout
git reset
57
Introduction to the C++ Ecosystem Git
Committing Changes
58
Introduction to the C++ Ecosystem Git
59
Introduction to the C++ Ecosystem Git
git diff
View the changes in the working directory (without the staging area).
git diff --staged
View the changes in the staging area (without the working directory).
git diff HEAD
View the changes in the working directory and the staging area.
git diff branch1..branch2
View the changes between two branches (or tags, commits).
Example output of git diff
diff --git a/foo b/foo
index e965047..980a0d5 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-Hello
+Hello World!
60
Introduction to the C++ Ecosystem Git
git branch
Show all branches and which one is active.
git branch <name>
Create a new branch that points to the current commit (HEAD).
git checkout <name>
Switch to another branch, i.e. change all files in the working directory so that
they are equal to the tree of the other branch.
git checkout -b <name>
Create a branch and switch to it.
git tag
Show all tags.
git tag [-s] <name>
Create a new tag that points to the current commit. Is signed with PGP when
-s is given.
61
Introduction to the C++ Ecosystem Git
master
C1 C2 C3
C4 C5
feature-abc
C1 C2 C3 Cm C1 C2 C3 C 40 C50
C4 C5
62
Introduction to the C++ Ecosystem Git
63
Introduction to the C++ Ecosystem Git
64
Introduction to the C++ Ecosystem Git
65
Introduction to the C++ Ecosystem Git
66
Introduction to the C++ Ecosystem Git
• Sometimes, especially when reading a new code base, you want to know
which commit changed a certain line
• Also, sometimes you want to know who wrote a certain line
git blame <filename>
• Shows the given file with commit annotations
• Each line starts with the commit hash, the name of the author, and the
commit date
tig blame <filename>
• Like git blame but with a nicer interface
• Allows to “re-blame” at a given line, i.e. showing the selected line in the
version just before it was last modified
67
Introduction to the C++ Ecosystem Git
.gitignore
• git status, git diff, etc. usually look at all files in all subdirectories of
the repository
• If files or directories should always be excluded (e.g. build or cache
directories), they can be added to the .gitignore file
• This file contains one entry per line, lines starting with # are skipped:
foo.txt Ignores all files named foo.txt
/foo.txt Ignores only the file foo.txt in the top-level directory
foo/ Ignores all directories and their contents named foo
*f* Ignores all files and directories that contain the letter f
.git
• This directory contains all commits, branches, etc.
• E.g., .git/refs/heads contains one file per branch
• If you remove this directory, all data is lost!
68
Basic C++ Syntax
69
Basic C++ Syntax
Overview
70
Basic C++ Syntax
Look at these links and familiarize yourself with the reference documentation!
71
Basic C++ Syntax Basic Types and Variables
Fundamental Types
All other types are composed of these fundamental types in some way
72
Basic C++ Syntax Basic Types and Variables
Void Type
73
Basic C++ Syntax Basic Types and Variables
Boolean Type
74
Basic C++ Syntax Basic Types and Variables
Signedness modifiers
• signed integers will have signed representation (i.e. they can represent
negative numbers)
• unsigned integers will have unsigned representation (i.e. they can only
represent non-negative numbers)
Size modifiers
• short integers will be optimized for space (at least 16 bits wide)
• long integers will be at least 32 bits wide
• long long integers will be at least 64 bits wide
75
Basic C++ Syntax Basic Types and Variables
By default integers are signed, thus the signed keyword can be omitted
// e and f have the same type
signed int e;
int f;
77
Basic C++ Syntax Basic Types and Variables
#include <cstdint>
78
Basic C++ Syntax Basic Types and Variables
Character Types
Character types represent character codes and (to some extent) integral values
• Identified by C++ keywords signed char and unsigned char
• Minimum width is 8 bit, large enough to represent UTF-8 eight-bit code units
• The C++ type char may either be signed char or unsigned char,
depending on the implementation
• signed char and unsigned char are sometimes used to represent small
integral values
79
Basic C++ Syntax Basic Types and Variables
80
Basic C++ Syntax Basic Types and Variables
81
Basic C++ Syntax Basic Types and Variables
Example
uint16_t i = 257;
uint8_t j = i; // j is 1
if (j) {
/* executed if j is not zero */
}
82
Basic C++ Syntax Basic Types and Variables
83
Basic C++ Syntax Basic Types and Variables
Example
foo.cpp foo.o
int foo(int i) { foo(int):
if ((i + 1) > i) movl $42, %eax
return 42; retq
return 123;
}
84
Basic C++ Syntax Basic Types and Variables
Variables
void foo() {
unsigned i = 0, j;
unsigned meaningOfLife = 42;
}
85
Basic C++ Syntax Basic Types and Variables
Important differences
• Options 1 and 2 simply assign the value of the expression to the variable,
possibly invoking implicit type conversions
• Option 3 results in a compile error if implicit type conversions potentially
result in loss of information
86
Basic C++ Syntax Basic Types and Variables
double a = 3.1415926;
double b(42);
unsigned c = a; // OK: c == 3
unsigned d(b); // OK: d == 42
unsigned e{a}; // ERROR: potential information loss
unsigned f{b}; // ERROR: potential information loss
87
Basic C++ Syntax Basic Types and Variables
Integer Literals
Integer literals represent constant values embedded in the source code
• Decimal: 42
• Octal: 052
• Hexadecimal: 0x2a
• Binary: 0b101010
88
Basic C++ Syntax Basic Types and Variables
Floating-point literals
One of the following suffixes may be appended to a literal to specify its type
• float suffix: 1.0f or 1.0F
• long double suffix: 1.0l or 1.0L
89
Basic C++ Syntax Basic Types and Variables
Character Literals
One of the following prefixes may be prepended to a literal to specify its type
• UTF-8 prefix: u8'a', u8'b'
• UTF-16 prefix: u'a', u'b'
• UTF-32 prefix: U'a', U'b'
90
Basic C++ Syntax Basic Types and Variables
Any type T in C++ (except function and reference types) can be cv-qualified
• const-qualified: const T
• volatile-qualified: volatile T
• cv-qualifiers can appear in any order, before or after the type
Semantics
• const objects cannot be modified
• Any read or write access to a volatile object is treated as a visible side
effect for the purposes of optimization
• volatile should be avoided in most cases (it is likely to be deprecated in
future versions of C++)
• Use atomics instead (more details later)
91
Basic C++ Syntax Basic Types and Variables
int main() {
int a = 1; // will be optimized out
int b = 2; // will be optimized out
volatile int c = 42;
volatile int d = c + b;
}
main:
movl $42, -4(%rsp) # volatile int c = 42
movl -4(%rsp), %eax # volatile int d = c + b;
addl $2, %eax # volatile int d = c + b;
movl %eax, -8(%rsp) # volatile int d = c + b;
movl $0, %eax # implicit return 0;
ret
92
Basic C++ Syntax Expressions
Expression Fundamentals
Fundamental expressions
• Variable names
• Literals
93
Basic C++ Syntax Expressions
Value Categories
Broadly (and inaccurately) there are two value categories: lvalues and rvalues
• lvalues refer to the identity of an object
• rvalues refer to the value of an object
• Modifiable lvalues can appear on the left-hand side of an assignment
• lvalues and rvalues can appear on the right-hand side of an assignment
94
Basic C++ Syntax Expressions
Operator Explanation
+a Unary plus
-a Unary minus
a + b Addition
a - b Subtraction
a * b Multiplication
a / b Division
a % b Modulo
~a Bitwise NOT
a & b Bitwise AND
a | b Bitwise OR
a ^ b Bitwise XOR
a << b Bitwise left shift
a >> b Bitwise right shift
95
Basic C++ Syntax Expressions
Incorrectly using the arithmetic operators can lead to undefined behavior, e.g.
• Signed overflow (see above)
• Division by zero
• Shift by a negative offset
• Shift by an offset larger than the width of the type
96
Basic C++ Syntax Expressions
Operator Explanation
!a Logical NOT
a && b Logical AND (short-circuiting)
a || b Logical OR (short-circuiting)
a == b Equal to
a != b Not equal to
a < b Less than
a > b Greater than
a <= b Less than or equal to
a >= b Greater than or equal to
97
Basic C++ Syntax Expressions
Operator Explanation
a = b Simple assignment
a += b Addition assignment
a -= b Subtraction assignment
a *= b Multiplication assignment
a /= b Division assignment
a %= b Modulo assignment
a &= b Bitwise AND assignment
a |= b Bitwise OR assignment
a ^= b Bitwise XOR assignment
a <<= b Bitwise left shift assignment
a >>= b Bitwise right shift assignment
Notes
• The left-hand side of an assignment operator must be a modifiable lvalue
• For built-in types a OP= b is equivalent to a = a OP b except that a is only
evaluated once
98
Basic C++ Syntax Expressions
unsigned a, b, c;
a = b = c = 42; // a, b, and c have value 42
if (unsigned d = computeValue()) {
// executed if d is not zero
} else {
// executed if d is zero
}
99
Basic C++ Syntax Expressions
Operator Explanation
++a Prefix increment
--a Prefix decrement
a++ Postfix increment
a-- Postfix decrement
100
Basic C++ Syntax Expressions
Operator Explanation
a?b:c Conditional operator
Semantics
• a is evaluated and converted to bool
• If the result was true, b is evaluated
• Otherwise c is evaluated
The type and value category of the resulting expression depend on the operands
101
Basic C++ Syntax Expressions
102
Basic C++ Syntax Expressions
103
Basic C++ Syntax Expressions
104
Basic C++ Syntax Expressions
105
Basic C++ Syntax Expressions
106
Basic C++ Syntax Statements
Simple Statements
int i = 0;
{ // start of block
int i = 0; // declaration statement
} // end of block, i goes out of scope
int i = 1; // declaration statement
107
Basic C++ Syntax Statements
Scope
int a = 21;
int b = 0;
{
int a = 1; // scope of the first a is interrupted
int c = 2;
b = a + c + 39; // a refers to the second a, b == 42
} // scope of the second a and c ends
b = a; // a refers to the first a, b == 21
b += c; // ERROR: c is not in scope
108
Basic C++ Syntax Statements
If Statement (1)
if (init-statement; condition)
then-statement
else
else-statement
Explanation
• If condition evaluates to true after conversion to bool, then-statement is
executed, otherwise else-statement is executed
• Both init-statement and the else branch can be omitted
• If present, init-statement must be an expression or declaration statement
• condition must be an expression statement or a single declaration
• then-statement and else-statement can be arbitrary (compound) statements
109
Basic C++ Syntax Statements
If Statement (2)
The init-statement form is useful for local variables only needed inside the if
Equivalent formulation
{
unsigned value = computeValue();
if (value < 42) {
// do something
} else {
// do something else
}
}
110
Basic C++ Syntax Statements
If Statement (3)
In nested if-statements, the else is associated with the closest if that does not
have an else
// INTENTIONALLY BUGGY!
if (condition0)
if (condition1)
// do something if (condition0 && condition1) == true
else
// do something if condition0 == false
// Working as intended
if (condition0) {
if (condition1)
// do something if (condition0 && condition1) == true
} else {
// do something if condition0 == false
}
111
Basic C++ Syntax Statements
Explanation
• condition may be an expression or single declaration that is convertible to an
enumeration or integral type
• The body of a switch statement may contain an arbitrary number of
case constant: labels and up to one default: label
• The constant values for all case: labels must be unique
• If condition evaluates to a value for which a case: label is present, control is
passed to the labelled statement
• Otherwise, control is passed to the statement labelled with default:
• The break; statement can be used to exit the switch
112
Basic C++ Syntax Statements
Regular example
switch (computeValue()) {
case 21:
// do something if computeValue() was 21
break;
case 42:
// do something if computeValue() was 42
break;
default:
// do something if computeValue() was != 21 and != 42
break;
}
113
Basic C++ Syntax Statements
switch (computeValue()) {
case 21:
case 42:
// do something if computeValue() was 21 or 42
break;
default:
// do something if computeValue() was != 21 and != 42
break;
}
114
Basic C++ Syntax Statements
While Loop
while (condition)
statement
Explanation
• Executes statement repeatedly until the value of condition becomes false.
The test takes place before each iteration.
• condition may be an expression that can be converted to bool or a single
declaration
• statement may be an arbitrary statement
• The break; statement may be used to exit the loop
• The continue; statement may be used to skip the remainder of the body
115
Basic C++ Syntax Statements
Do-While Loop
do
statement
while (condition);
Explanation
• Executes statement repeatedly until the value of condition becomes false.
The test takes place after each iteration.
• condition may be an expression that can be converted to bool or a single
declaration
• statement may be an arbitrary statement
• The break; statement may be used to exit the loop
• The continue; statement may be used to skip the remainder of the body
116
Basic C++ Syntax Statements
unsigned i = 42;
do {
// executed once
} while (i < 42);
117
Basic C++ Syntax Statements
Explanation
• Executes init-statement once, then executes statement and
iteration-expression repeatedly until condition becomes false
• init-statement may either be an expression or declaration
• condition may either be an expression that can be converted to bool or a
single declaration
• iteration-expression may be an arbitrary expression
• All three of the above statements may be omitted
• The break; statement may be used to exit the loop
• The continue; statement may be used to skip the remainder of the body
118
Basic C++ Syntax Statements
119
Basic C++ Syntax Functions
Functions in C++
• Associate a sequence of statements (the function body) with a name
• Functions may have zero or more function parameters
• Functions can be invoked using a function-call expression which initializes the
parameters from the provided arguments
name ( argument-list );
120
Basic C++ Syntax Functions
unsigned meaningOfLife() {
// extremely complex computation
return 42;
}
int main() {
// run the program
}
121
Basic C++ Syntax Functions
122
Basic C++ Syntax Functions
Argument Passing
unsigned square(unsigned v) {
v = v * v;
return v;
}
int main() {
unsigned v = 8;
unsigned w = square(v); // w == 64, v == 8
}
C++ differs from other programming languages (e.g. Java) in this respect
• Parameters can explicitly be passed by reference (more details later)
• Essential to keep argument-passing semantics in mind, especially when
used-defined classes are involved (more details later)
123
Basic C++ Syntax Functions
Default Arguments
A function definition can include default values for some of its parameters
• Indicated by including an initializer for the parameter
• After a parameter with a default value, all subsequent parameters must have
default values as well
• Parameters with default values may be omitted when invoking the function
int main() {
int x = foo(1); // x == 6
int y = foo(1, 1); // y == 5
int z = foo(1, 1, 1); // z == 3
}
124
Basic C++ Syntax Functions
125
Basic C++ Syntax Functions
Valid example
int main() {
foo(1u); // calls foo(unsigned)
foo(1.0f); // calls foo(float)
}
126
Basic C++ Syntax Basic IO
Basic IO (1)
#include <iostream>
// ----------------------------------
int main() {
unsigned i = 42;
std::cout << "The value of i is " << i << std::endl;
}
127
Basic C++ Syntax Basic IO
Basic IO (2)
#include <iostream>
// ----------------------------------
int main() {
std::cout << "Please enter a value: ";
unsigned v;
std::cin >> v;
std::cout << "You entered " << v << std::endl;
}
128
Compiling C++ files
129
Compiling C++ files Hello World 2.0
In C++ the code is usually separated into header files (.h/.hpp) and
implementation files (.cpp/.cc):
sayhello.hpp
#include <string>
void sayhello(const std::string& name);
sayhello.cpp
#include "sayhello.hpp"
#include <iostream>
void sayhello(const std::string& name) {
std::cout << "Hello " << name << '!' << std::endl;
}
Other code that wants to use this function only has to include sayhello.hpp.
130
Compiling C++ files Compiler
Compiler
Reminder: Internally, the compiler is divided into Preprocessor, Compiler, and
Linker.
Preprocessor:
• Takes an input file of (almost) any programming language
• Handles all preprocessor directives (i.e., all lines starting with #) and macros
• Outputs the file without any preprocessor directives or macros
Compiler:
• Takes a preprocessed C++ (or C) file, called translation unit
• Generates and optimizes the machine code
• Outputs an object file
Linker:
• Takes multiple object files
• Can also take references to other libraries
• Finds the address of all symbols (e.g., functions, global variables)
• Outputs an executable file or a shared library
131
Compiling C++ files Compiler
Preprocessor
Most common preprocessor directives:
• #include: Copies (!) the contents of a file into the current file
#ifdef FOO
...
#endif
132
Compiling C++ files Compiler
Compiler
• Every translation unit (usually a .cpp file) results in exactly one object file
(usually .o)
• References to external symbols (e.g., functions that are defined in another
.cpp) are not resolved
mul.cpp
int add(int a, int b);
int mul(int a, int b) {
if (a > 0) { return add(a, mul(a - 1, b)); }
else { return 0; }
}
Linker
• The linker usually does not have to know about any programming language
• Still, some problems with your C++ code will only be found by the linker and
not by the compiler (e.g., ODR violations, see later slides)
• Most common error are missing symbols, happens either because you forgot
to define a function or global variable, or forgot to add a library
• Popular linkers are: GNU ld, GNU gold, lld (by the LLVM project)
134
Compiling C++ files Compiler
135
Compiling C++ files Debugging
• Debugging by printing text is easy but most of the time not very useful
• Especially for multi-threaded programs a real debugger is essential
• For C++ the most used debugger is gdb (“GNU debugger”)
• It is free and open-source (GPLv2)
• For the best debugging experience a program should be compiled without
optimizations (-O0) and with debug symbols (-g)
• The debug symbols help the debugger to map assembly instructions to the
source code that generated them
• The documentation for gdb can be found here:
https://sourceware.org/gdb/current/onlinedocs/gdb/
136
Compiling C++ files Debugging
137
Compiling C++ files Debugging
frame Show the currently selected stack frame, i.e. the current
stack with its local variables. Usually includes the function
name and the current source line. Can also be used to
switch to another frame.
backtrace Show all stack frames.
up Select the frame from the next higher function.
down Select the frame from the next lower function.
watch Set a watchpoint. When the memory address that is
watched is read or written, the debugger stops.
thread Show the currently selected thread in a multi-threaded pro-
gram. Can also be used to switch to another thread.
Most commands also have a short version, e.g., r for run, c for continue, etc.
138
Declarations and Definitions
139
Declarations and Definitions Objects
Objects
One of the core concepts of C++ are objects.
• The main purpose of C++ programs is to interact with objects in order to
achieve some goal
• Examples of objects are local and global variables
• Examples of concepts that are not objects are functions, references, and
values
140
Declarations and Definitions Objects
141
Declarations and Definitions Objects
142
Declarations and Definitions Objects
Lifetime
In addition to their storage duration objects also have a lifetime which is closely
related. References also have a lifetime.
• The lifetime of an object or reference starts when it was fully initialized
• The lifetime of an object ends when its destructor is called (for objects of
class types, see future lecture) or when its storage is deallocated or reused
(for all other types)
• The lifetime of an object never exceeds its storage duration.
• The lifetime of a reference ends as if it were a “scalar” object (e.g. an int
variable)
Generally, using an object outside of its lifetime leads to undefined behavior.
143
Declarations and Definitions Namespaces
Namespaces (1)
Larger projects may contain many names (functions, classes, etc.)
• Should be organized into logical units
• May incur name clashes
• C++ provides namespaces for this purpose
Namespace definitions
namespace identifier {
namespace-body
}
Explanation
• identifier may be a previously unused identifier, or the name of a namespace
• namespace-body may be a sequence of declarations
• A name declared inside a namespace must be qualified when accessed from
outside the namespace (:: operator)
144
Declarations and Definitions Namespaces
Namespaces (2)
Qualified name lookup
namespace A {
void foo() { /* do something */ }
void bar() {
foo(); // refers to A::foo
}
}
namespace B {
void foo() { /* do something */ }
}
int main() {
A::foo(); // qualified name lookup
B::foo(); // qualified name lookup
145
Declarations and Definitions Namespaces
Namespaces (3)
namespace A { namespace B {
void foo() { /* do something */ }
}}
// equivalent definition
namespace A::B {
void bar() {
foo(); // refers to A::B::foo
}
}
int main() {
A::B::bar();
}
146
Declarations and Definitions Namespaces
Namespaces (4)
Code can become rather confusing due to large number of braces
• Use visual separators (comments) at sensible points
• (Optionally) add comments to closing namespace braces
//----------------------------------
namespace A::B {
//----------------------------------
void foo() {
// do something
}
//----------------------------------
void bar() {
// do something else
}
//----------------------------------
} // namespace A::B
//----------------------------------
147
Declarations and Definitions Namespaces
Namespaces (5)
• Always using fully qualified names makes code easier to read
• Sometimes it is obvious from which namespace the names come from in
which case one prefers to use unqalified names
• For this using and using namespace can be used
• using namespace X imports all names from namespace X into the current
one
• using X::a only imports the name a from X into the current namespace
namespace A { int x; }
namespace B { int y; int z; }
using namespace A;
using B::y;
int main() {
x = 1; // Refers to A::x
y = 2; // Refers to B::y
z = 3; // ERROR: z was not declared in this scope
B::z = 3; // OK
}
148
Declarations and Definitions Declarations
Declarations
C++ code that introduces a name that can then be referred to is called
declaration. There are many different kinds of declarations:
• variable declarations: int a;
• function declarations: void foo();
• namespace declarations: namespace A { }
• using declarations: using A::x;
• class declarations: class C;
• template declarations: template <typename T> void foo();
• ...
149
Declarations and Definitions Declarations
Declaration Specifiers
Some declarations can also contain additional specifiers. The following lists shows
a few common ones and where they can be used. We will see some more specifiers
in future lectures.
static Can be used for variable and function declarations, affects the
declaration’s linkage (see next slide). Also, objects declared with
static have static storage duration.
extern Can be used for variable declarations in which case it also affects
their linkage. Objects declared with extern also have static
storage duration.
inline Can be used for variable and function declarations. Despite the
name, has (almost) nothing to do with the inlining optimization.
See the slides about the “One Definition Rule” for more
information.
150
Declarations and Definitions Declarations
Linkage
Most declarations have a (conceptual) property called linkage. This property
determines how the name of the declaration will be visible in the current and in
other translation units. There are three types of linkage:
no linkage:
• Names can only be referenced from the scope they are in
• Local variables
internal linkage:
• Names can only be referenced from the same translation unit
• Global functions and variables declared with static
• Global variables that are not declared with extern
• All declarations in namespaces without name (“anonymous namespaces”)
external linkage:
• Names can be referenced from other translation units
• Global functions (without static)
• Global variables with extern
151
Declarations and Definitions Definitions
Definitions
When a name is declared it can be referenced by other code. However, most uses
of a name also require the name to be defined in addition to be declared.
Formally, this is called odr-use and covers the following cases:
• The value of a variable declaration is read or written
• The address of a variable or function declaration is taken
• A function is called
• An object of a class declaration is used
Most declarations are also definitions, with some exceptions such as
• Any declaration with an extern specifier and no initializer
• Function declarations without function bodies
• Declaration of a class name (“forward declaration”)
152
Declarations and Definitions Definitions
153
Declarations and Definitions Definitions
a.cpp
int i = 5; // OK: declares and defines i
int i = 6; // ERROR: redefinition of i
154
Declarations and Definitions Definitions
155
Declarations and Definitions Definitions
156
Declarations and Definitions Header and Implementation Files
Header files can be included in implementation files and other headers through
preprocessor directives.
Syntax:
• #include "path" where path is a relative path to the header file
• #include <path> like the first version but only system directories are
searched for the path
157
Declarations and Definitions Header and Implementation Files
path/B.hpp
#include "path/A.hpp"
int j = i;
main.cpp
#include "path/A.hpp"
#include "path/B.hpp" // ERROR: i is defined twice
158
Declarations and Definitions Header and Implementation Files
path/B.hpp
#ifndef H_path_B
#define H_path_B
// ----------------------------------
#include "path/A.hpp"
// ----------------------------------
int j = i;
// ----------------------------------
#endif
159
Declarations and Definitions Header and Implementation Files
path/B.hpp
#pragma once
// ----------------------------------
#include "path/A.hpp"
// ----------------------------------
int j = i;
160
Declarations and Definitions Header and Implementation Files
The example CMake project from last lecture shows how header and
implementation files are used. These are the header files:
sayhello.hpp
#pragma once
#include <string>
saybye.hpp
#pragma once
#include <string>
161
Declarations and Definitions Header and Implementation Files
saybye.cpp
#include "saybye.hpp"
#include <iostream>
162
Declarations and Definitions Header and Implementation Files
163
References, Arrays, and Pointers
164
References, Arrays, and Pointers
Overview
Much of the power of C++ comes from the ability to define compound types
• Functions
• Classes (covered in the next lecture)
• References
• Arrays
• Pointers
165
References, Arrays, and Pointers References
166
References, Arrays, and Pointers References
The & or && tokens are part of the declarator, not the type
int i = 10;
int &j = i, k = i; // j is reference to int, k is int
However, we may omit or insert whitespaces before and after the & or && tokens
• Both int& j = i; and int &j = i; are valid C++
• By convention, we use the former notation (int& j = i;)
• To avoid confusion, statements should declare only one identifier at a time
• Very rarely, exceptions to this rule are necessary in the init-statements of if
and switch statements as well as for loops
167
References, Arrays, and Pointers References
Reference Initialization
Exceptions
• Function parameter declarations
• Function return type declarations
• Class member declarations (more details later)
• With the extern specifier
168
References, Arrays, and Pointers References
unsigned i = 10;
unsigned j = 42;
unsigned& r = i; // r is an alias for i
r = 21; // modifies i to be 21
r = j; // modifies i to be 42
i = 123;
j = r; // modifies j to be 123
169
References, Arrays, and Pointers References
int main() {
int i = 10;
foo(i); // i == 52
foo(i); // i == 94
}
170
References, Arrays, and Pointers References
int global0 = 0;
int global1 = 0;
int main() {
foo(0) = 42; // global0 == 42
foo(1) = 14; // global1 == 14
}
171
References, Arrays, and Pointers References
int i = 10;
int&& j = i; // ERROR: Cannot bind rvalue reference to lvalue
int&& k = 42; // OK
int i = 10;
int j = 32;
int&& k = i + j; // k == 42
k += 42; // k == 84;
172
References, Arrays, and Pointers References
int& bar();
int baz();
int main() {
int i = 42;
const int j = 84;
173
References, Arrays, and Pointers References
int i = 10;
const int& j = i;
int& k = j; // ERROR: binding reference of type int& to
// const int discards cv-qualifiers
j = 42; // ERROR: assignment of read-only reference
int i = 10;
int j = 32;
const int& k = i + j; // OK, but k is immutable
174
References, Arrays, and Pointers References
Dangling references
Example
int& foo() {
int i = 42;
return i; // ERROR: Returns dangling reference
}
175
References, Arrays, and Pointers Arrays
176
References, Arrays, and Pointers Arrays
a = b; // ERROR: a is an array
177
References, Arrays, and Pointers Arrays
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0a 00
00 02 04 06 08 0a 0c 0e 10 12
Address
178
References, Arrays, and Pointers Arrays
179
References, Arrays, and Pointers Arrays
Array Initialization
Multi-dimensional arrays may also be list-initialized, but only the first dimension
may have unknown bound
180
References, Arrays, and Pointers Arrays
std::array
Example
#include <array>
int main() {
std::array<unsigned short, 10> a;
for (unsigned i = 0; i < a.size(); ++i)
a[i] = i + 1; // no bounds checking
}
181
References, Arrays, and Pointers Arrays
std::vector (1)
Useful functions
• push_back – inserts an element at the end of the vector
• size – queries the current size
• clear – clears the contents
• resize – change the number of stored elements
• The subscript operator can be used with similar semantics as for C-style
arrays
182
References, Arrays, and Pointers Arrays
std::vector (2)
Example
#include <iostream>
#include <vector>
int main() {
std::vector<unsigned short> a;
for (unsigned i = 0; i < 10; ++i)
a.push_back(i + 1);
183
References, Arrays, and Pointers Arrays
Range-For (1)
Explanation
• Executes init-statement once, then executes loop-statement once for
each element in the range defined by range-expression
• range-expression may be an expression that represents a sequence (e.g.
an array or an object for which begin and end functions are defined, such as
std::vector)
• range-declaration should declare a named variable of the element type of
the sequence, or a reference to that type
• init-statement may be omitted
184
References, Arrays, and Pointers Arrays
Range-For (2)
Example
#include <iostream>
#include <vector>
int main() {
std::vector<unsigned short> a;
// range-for
for (const unsigned short& e : a)
std::cout << e << std::endl;
}
185
References, Arrays, and Pointers Pointers
Storage of Objects
186
References, Arrays, and Pointers Pointers
Notes
• A pointer to an object represents the address of the first byte in memory that
is occupied by that object
• As opposed to references, pointers are themselves objects
• Consequently, pointers to pointers are allowed
187
References, Arrays, and Pointers Pointers
Pointer-to-pointer declarations
Contraptions like the declaration of f are very rarely (if at all) necessary
188
References, Arrays, and Pointers Pointers
Example
int a = 10;
const int b = 42;
int* c = &a; // OK: c points to a
const int* d = &b; // OK: d points to b
int* e = &b; // ERROR: invalid conversion from
// const int* to int*
189
References, Arrays, and Pointers Pointers
Example
int a = 10;
int* c = &a;
int& d = *c; // reference to a
d = 123; // a == 123
*c = 42; // a == 42
190
References, Arrays, and Pointers Pointers
Null Pointers
return v;
}
191
References, Arrays, and Pointers Pointer Arithmetic
Example
#include <iostream>
int main() {
int array[3] = {123, 456, 789};
const int* ptr = array;
192
References, Arrays, and Pointers Pointer Arithmetic
Example
#include <iostream>
int main() {
int array[3] = {123, 456, 789};
const int* ptr = array;
193
References, Arrays, and Pointers Pointer Arithmetic
194
References, Arrays, and Pointers Pointer Arithmetic
#include <iostream>
int main() {
int array[3] = {123, 456, 789};
const int* ptr = &array[1];
195
References, Arrays, and Pointers Pointer Arithmetic
Example
int main() {
std::vector<int> v;
v.resize(10);
196
References, Arrays, and Pointers Pointer Arithmetic
Example
#include <iostream>
int main() {
int array[3] = {123, 456, 789};
const int* ptr1 = &array[0];
const int* ptr2 = &array[3]; // past-the-end pointer
std::cout << "There are " << (ptr2 - ptr1) << " elements ";
std::cout << "in array" << std::endl;
}
197
References, Arrays, and Pointers Pointer Arithmetic
Comparisons on Pointers
Example
#include <iostream>
int main() {
int array[3] = {123, 456, 789};
198
References, Arrays, and Pointers Pointer Conversions
Void Pointers
Usage
• Used to pass objects of unknown type
• Extensively used in C interfaces (e.g. malloc, qsort, ...)
• Only few operations are defined on void pointers (mainly assignment)
• In order to use the pointed-to object, one must cast the void pointer to the
required type
199
References, Arrays, and Pointers Pointer Conversions
static_cast (1)
Explanation
• Converts the value of expression to a value of new_type
• new_type must be at least as cv-qualified as the type of expression
• Can be used to convert void pointers to pointers of another type
• Many more use cases (see reference documentation)
200
References, Arrays, and Pointers Pointer Conversions
static_cast (2)
Void pointers
int i = 42;
void* vp = &i;
int* ip = static_cast<int*>(vp);
int main() {
int a = 42;
double b = 3.14;
201
References, Arrays, and Pointers Pointer Conversions
reinterpret_cast
Explanation
• Interprets the underlying bit pattern of the value of expression as a value
of new_type
• new_type must be at least as cv-qualified as the type of expression
• Usually does not generate any CPU instructions
202
References, Arrays, and Pointers Pointer Conversions
203
References, Arrays, and Pointers Pointer Conversions
int main() {
int a = 0;
double* y = reinterpret_cast<double*>(&a);
return foo(&a, y);
}
Compiling this with g++ -O1 will result in the following assembly
foo.o
main:
movl $0, %eax
ret
204
References, Arrays, and Pointers Pointer Conversions
int main() {
int a = 0;
double* y = reinterpret_cast<double*>(&a);
return foo(&a, y);
}
Compiling this with g++ -O2 will result in the following assembly
foo.o
main:
movl $42, %eax
ret
205
References, Arrays, and Pointers Pointer Conversions
206
References, Arrays, and Pointers Pointer Conversions
#include <iostream>
#include <iomanip>
#include <cstddef>
int main() {
double a = 3.14;
const std::byte* bytes = reinterpret_cast<const std::byte*>(&a);
207
References, Arrays, and Pointers Pointer Conversions
uintptr_t
Any pointer may legally be converted to an integral type
• The integral type must be large enough to hold all values of the pointer
• Usually, uintptr_t should be used (defined in <cstdint> header)
• Useful in some cases, especially when building custom data structures (more
details later)
Example
#include <cstddint>
#include <iostream>
int main() {
int x = 42;
uintptr_t addr = reinterpret_cast<uintptr_t>(&x);
208
References, Arrays, and Pointers Pointer Conversions
The sizeof operator queries the size of the object representation of a type
sizeof( type )
Explanation
• The size of a type is given in bytes
• sizeof(std::byte), sizeof(char), and sizeof(unsigned char)
return 1 by definition
• Depending on the computer architecture, there may be 8 or more bits in one
byte (as defined by C++)
209
References, Arrays, and Pointers Pointer Conversions
int main() {
int array[3] = {123, 456, 789};
std::cout << "(ptr1 - ptr0) = " << (ptr1 - ptr0) << std::endl;
std::cout << "(uptr1 - uptr0) = " << (uptr1 - uptr0) << std::endl;
}
210
References, Arrays, and Pointers Pointer Conversions
$ ./foo
sizeof(int) = 4
(ptr1 - ptr0) = 1
(uptr1 - uptr0) = 4
Interpretation
• One int occupies 4 bytes
• There is one int between ptr0 and ptr1
• There are 4 bytes (i.e. exactly one int) between ptr0 and ptr1
211
References, Arrays, and Pointers Pointer Conversions
alignof( type )
Explanation
• Depending on the computer architecture, certain types must have addresses
aligned to specific byte boundaries
• The alignof operator returns the number of bytes between successive
addresses where an object of type can be allocated
• The alignment requirement of a type is always a power of two
• Important (e.g.) for SIMD instructions, where the programmer must
explicitly ensure correct alignment
• Memory accesses with incorrect alignment lead to undefined behavior, e.g.
SIGSEGV or SIGBUS (depending on architecture)
212
References, Arrays, and Pointers Guidelines
Usage Guidelines
When to use references
• Pass-by-reference function call semantics
• When it is guaranteed that the referenced object will always be valid
• When object that should be referenced is always the same
213
References, Arrays, and Pointers Troubleshooting
Troubleshooting
214
References, Arrays, and Pointers Troubleshooting
Obvious example
foo.cpp
int main() {
int* a;
return *a; // ERROR: Dereferencing an uninitialized pointer
}
$ ./foo
[1] 5128 segmentation fault (core dumped) ./foo
215
References, Arrays, and Pointers Troubleshooting
int main() {
long* ptr;
long array[3] = {123, 456, 789};
ptr = &array[0];
array[3] = 987; // ERROR: off-by-one access
return *ptr;
}
216
References, Arrays, and Pointers Troubleshooting
The address sanitizer (ASAN) is one of the most powerful debugging tools
• Enable with the g++ flag -fsanitize=address
• Instruments memory access instructions to check for common bugs
• Should normally be used in conjunction with -g for debug builds
• Should be enabled by default in your debug builds, unless there is a very
compelling reason against it
217
References, Arrays, and Pointers Troubleshooting
return *ptr;
}
218
References, Arrays, and Pointers Troubleshooting
219
Classes
Classes
220
Classes
Classes
221
Classes Members
Data Members
• Declarations of data members are variable declarations
• extern is not allowed
• Declarations without static are called non-static data members, otherwise
they are static data members
• thread_local is only allowed for static data members
• Declaration must have a complete type (see later slide)
• Name of the declaration must differ from the class name and must be unique
within the class
• Non-static data members can have a default value
struct Foo {
// non-static data members:
int a = 123;
float& b;
const char c;
// static data members:
static int s;
thread_local static int t;
};
222
Classes Members
• Every type has a size and an alignment requirement (see last lecture)
• To be compatible between different compilers and programming languages
(mainly C), the memory layout of objects of class type is fixed
• Non-static data members appear in memory by the order of their declarations
• Size and alignment of each data-member is accounted for → leads to “gaps”
in the object, called padding bytes
• Alignment of a class type is equal to the largest alignment of all non-static
data members
• Size of a class type is at least the sum of all sizes of all non-static data
members and at least 1
• static data members are stored separately
223
Classes Members
sizeof(i) == 4
alignof(i) == 4 offset
struct C { 00 01 02 03 04 05 06 07
int i; sizeof(p) == 8
00 i padding
int* p; alignof(p) == 8
08 p
char b; sizeof(b) == 1
short s; 10 b s padding
alignof(b) == 1
};
sizeof(s) == 2
alignof(s) == 2
sizeof(C) == 24
alignof(C) == 8
Reordering the member variables in the order p, i, s, b would lead to
sizeof(C) == 16!
In general: Order member variables by decreasing alignment to get the fewest
padding bytes.
224
Classes Members
Member Functions
• Declarations of member functions are like regular function declarations
• Just like for data members, there are non-static and static (with the static
specifier) member functions
• Non-static member functions can be const-qualified (with const) or
ref-qualified (with const&, &, or &&)
• Non-static member functions can be virtual (see next lecture)
• There are some member functions with special functions:
• Constructor and destructor
• Overloaded operators
struct Foo {
void foo(); // non-static member function
void cfoo() const; // const-qualified non-static member function
void rfoo() &; // ref-qualified non-static member function
static void bar(); // static member function
Foo(); // Constructor
~Foo(); // Destructor
bool operator==(const Foo& f); // Overloaded operator ==
};
225
Classes Members
Accessing Members
226
Classes Members
struct C {
int i;
int foo() {
this->i; // Explicit member access, this has type C*
return i; // Implicit member access
}
int foo() const { return this->i; /* this has type const C* */ }
int bar() & { return i; /* this (implicit) has type C* */ }
int bar() const& { return this->i; /* this has type const C* */ }
};
227
Classes Members
Out-of-line Definitions
• Just like regular functions member functions can have separate declarations
and definitions
• A member function that is defined in the class body is said to have an inline
definition
• A member function that is defined outside of the class body is said to have
an out-of-line definition
• Member functions with inline definitions implicitly have the inline specifier
• Out-of-line definitions must have the same qualifiers as their declaration
struct Foo {
void foo1() { /* ... */ } // Inline definition
void foo2();
void foo_const() const;
static void foo_static();
};
// Out-of-line definitions
void Foo::foo2() { /* ... */ }
void Foo::foo_const() const { /* ... */ }
void Foo::foo_static() { /* ... */ }
228
Classes Forward Declarations
Use Cases
• Allows classes to refer to each other
• Can reduce compilation time (significantly) by avoiding transitive includes of
an expensive-to-compile header
• Commonly used in header files
229
Classes Forward Declarations
Example
foo.hpp
class A;
class ClassFromExpensiveHeader;
class B {
ClassFromExpensiveHeader* member;
foo.cpp
#include "expensive_header.hpp"
/* implementation */
230
Classes Forward Declarations
Incomplete Types
231
Classes Constructors and Destructors
Constructors
• Constructors are special functions that are called when an object is initialized
• Constructors have no return type, no const- or ref-qualifiers, and their name
is equal to the class name
• The definition of a constructor can have an initializer list
• Constructors can have arguments, a constructor without arguments is called
default constructor
• Constructors are sometimes implicitly defined by the compiler
232
Classes Constructors and Destructors
Initializer List
• The initializer list specifies how member variables are initialized before the
body of the constructor is executed
• Other constructors can be called in the initializer list
• Members should be initialized in the order of their definition
• Members are initialized to their default value if not specified in the list
• const member variables can only be initialized in the initializer list
struct Foo {
int a = 123; float b; const char c;
// default constructor initializes a (to 123), b, and c
Foo() : b(2.5), c(7) {}
// initializes a and b to the given values
Foo(int a, float b, char c) : a(a), b(b), c(c) {}
Foo(float f) : Foo() {
// First the default constructor is called, then the body
// of this constructor is executed
b *= f;
}
};
233
Classes Constructors and Destructors
Initializing Objects
234
Classes Constructors and Destructors
• Constructors with exactly one argument are treated specially: They are used
for explicit and implicit conversions
• If implicit conversion with such constructors is not desired, the keyword
explicit can be used to disallow it
• Generally, you should use explicit unless you have a good reason not to
235
Classes Constructors and Destructors
Copy Constructors
struct Foo {
Foo(const Foo& other) { /* ... */ }
};
void doFoo(Foo f);
Foo f;
Foo g(f); // Call copy constructor explicitly
doFoo(g); // Copy constructor is called implicitly
236
Classes Constructors and Destructors
Destructors
• The destructor is a special function that is called when the lifetime of an
object ends
• The destructor has no return type, no arguments, no const- or ref-qualifiers,
and its name is ~class-name
• For objects with automatic storage duration (e.g. local variables) the
destructor is called implicitly at the end of the scope in reverse order of their
definition
• Calling the destructor twice on the same object is undefined behavior
Foo a;
Bar b;
{
Baz c;
// c.~Baz() is called;
}
// b.~Bar() is called
// a.~Foo() is called
237
Classes Constructors and Destructors
Writing Destructors
struct Foo {
Bar a;
Bar b;
~Foo() {
std::cout << "Bye\n";
// b.~Bar() is called
// a.~Bar() is called
}
};
238
Classes Member Access Control
class Foo {
int a; // a is private
public:
// All following declarations are public
int b;
int getA() const { return a; }
protected:
// All following declarations are protected
int c;
public:
// All following declarations are public
static int getX() { return 123; }
};
239
Classes Member Access Control
Notes
• Friendship is non-transitive and cannot be inherited
• Access specifiers have no influence on friend declarations (i.e. they can
appear in private: or public: sections)
240
Classes Member Access Control
class A {
int a;
friend class B;
friend void foo(A&);
};
class B {
friend class C;
void foo(A& a) {
a.a = 42; // OK
}
};
class C {
void foo(A& a) {
a.a = 42; // ERROR
}
};
void foo(A& a) {
a.a = 42; // OK
}
241
Classes Member Access Control
Nested Types
struct A {
struct B {
int getI(const A& a) {
return a.i; // OK, B is friend of A
}
};
private:
int i;
};
Foo::Bar b; // reference nested type Bar of class Foo
242
Classes Constness of Members
struct Foo {
Expression Value Category
int i; foo.i non-const lvalue
const int c; foo.c const lvalue
mutable int m; foo.m non-const lvalue
} cfoo.i const lvalue
Foo& foo = /* ... */; cfoo.c const lvalue
const Foo& cfoo = /* ... */; cfoo.m non-const lvalue
243
Classes Constness of Members
244
Classes Constness of Members
struct Foo {
int a;
};
const Foo f{123};
Foo& fref = const_cast<Foo&>(f); // OK, cast is allowed
int b = fref.a; // OK, accessing value is allowed
fref.a = 42; // undefined behavior
245
Classes Constness of Members
class A {
int* numbers;
int& foo() {
int i = /* ... */;
// do some incredibly complicated computation to
// get a value for i
return numbers[i]
}
const int& foo() const {
// OK as long as foo() does not modify the object
return const_cast<A*>(this)->foo();
}
};
246
Classes Operator Overloading
Operator Overloading
• Classes can have special member functions to overload built-in operators like
+, ==, etc.
• Many overloaded operators can also be written as non-member functions
• Syntax: return-type operator op (arguments)
• Overloaded operator functions are selected with the regular overload
resolution
• Overloaded operators are not required to have meaningful semantics
• Almost all operators can be overloaded, exceptions are: :: (scope
resolution), . (member access), .* (member pointer access), ?: (ternary
operator)
• This includes “unusual” operators like: = (assignment), () (call),
* (dereference), & (address-of), , (comma)
247
Classes Operator Overloading
struct Int {
int i;
Int operator+(const Int& other) const { return Int{i + other.i}; }
};
bool operator==(const Int& a, const Int& b) const { return a.i == b.i; }
a + b; /* is equivalent to */ a.operator+(b);
a == b; /* is equivalent to */ operator==(a, b);
248
Classes Operator Overloading
C++ also has the unary + and − operators which can also be overloaded with
member functions.
struct Int {
int i;
Int operator+() const { return *this; };
Int operator-() const { return Int{-i}; };
};
Int a{123};
+a; /* is equivalent to */ a.operator+();
-a; /* is equivalent to */ a.operator-();
249
Classes Operator Overloading
struct Int {
int i;
Int& operator++() { ++i; return *this; }
Int operator--(int) { Int copy{*this}; --i; return copy; }
};
Int a{123};
++a; // a.i is now 124
a++; // ERROR: post-increment is not overloaded
Int b = a--; // b.i is 124, a.i is 123
--b; // ERROR: pre-decrement is not overloaded
250
Classes Operator Overloading
Subscript Operator
Classes that behave like containers or pointers usually override the subscript
operator [].
• a[b] is equivalent to a.operator[](b)
• Type of b can be anything, for array-like containers it is usually size_t
251
Classes Operator Overloading
Dereference Operators
Classes that behave like pointers usually override the operators * (dereference)
and -> (member of pointer).
• operator*() usually returns a reference
• operator->() should return a pointer or an object that itself has an
overloaded -> operator
252
Classes Operator Overloading
Assignment Operators
• The simple assignment operator is often used together with the copy
constructor and should have the same semantics (see also: future lecture
about copy and move semantics)
• All assignment operators usually return *this
struct Int {
int i;
Foo& operator=(const Foo& other) { i = other.i; return *this; }
Foo& operator+=(const Foo& other) { i += other.i; return *this; }
};
Foo a{123};
a = Foo{456}; // a.i is now 456
a += Foo{1}; // a.i is now 457
253
Classes Operator Overloading
Conversion Operators
A class C can use converting constructors to convert values of other types to type
C. Similarly, conversion operators can be used to convert objects of type C to
other types.
Syntax: operator type ()
• Conversion operators have the implicit return type type
• They are usually declared as const
• The explicit keyword can be used to prevent implicit conversions
• Explicit conversions are done with static_cast
• operator bool() is usually overloaded to be able to use objects in an if
statement
254
Classes Operator Overloading
Argument-Dependent Lookup
• Overloaded operators are usually defined in the same namespace as the type
of one of their arguments
• Regular unqualified lookup would not allow the following example to compile
• To fix this, unqualified names of functions are also looked up in the
namespaces of all arguments
• This is called Argument Dependent Lookup (ADL)
255
Classes Defaulted and Deleted Member Functions
struct Foo {
Bar b;
Foo() = default; /* equivalent to: */ Foo() {}
~Foo() = default; /* equivalent to: */ ~Foo() {}
struct Foo {
Foo(const Foo&) = delete;
};
Foo f; // Default constructor is defined implicitly
Foo g(f); // ERROR: copy constructor is deleted
257
Other User-Defined Types
258
Other User-Defined Types Unions
Unions
• In addition to regular classes declared with class or struct, there is
another special class type declared with union
• In a union only one member may be “active”, all members use the same
storage
• Size of the union is equal to size of largest member
• Alignment of the union is equal to largest alignment among members
• Strict aliasing rule still applies with unions!
• Most of the time there are better alternatives to unions, e.g.
std::array<char, N> or std::variant
Enums
• C++ also has user-defined enumeration types
• Typically used like integral types with a restricted range of values
• Also used to be able to use descriptive names instead of “magic” integer
values
• Syntax: enum-key name { enum-list };
• enum-key can be enum, enum class, or enum struct
• enum-list consists of comma-separated entries with the following syntax:
name [ = value ]
• When value is not specified, it is automatically chosen starting from 0
enum Color {
Red, // Red == 0
Blue, // Blue == 1
Green, // Green == 2
White = 10,
Black, // Black == 11
Transparent = White // Transparent == 10
};
260
Other User-Defined Types Enums
• Names from the enum list can be accessed with the scope resolution operator
• When enum is used as keyword, names are also introduced in the enclosing
namespace
• Enums declared with enum can be converted implicitly to int
• Enums can be converted to integers and vice versa with static_cast
• enum class and enum struct are equivalent
• Guideline: Use enum class unless you have a good reason not to
261
Other User-Defined Types Type Aliases
Type Aliases
• Names of types that are nested deeply in multiple namespaces or classes can
become very long
• Sometimes it is useful to declare a nested type that refers to another, existing
type
• For this type aliases can be used
• Syntax: using name = type;
• name is the name of the alias, type must be an existing type
• For compatibility with C type aliases can also be defined with typedef with
a different syntax but this should never be used in modern C++ code
262
Other User-Defined Types Type Aliases
In C++ the following aliases are defined in the std namespace and are commonly
used:
intN_t: Integer types with exactly N bits, usually defined for 8, 16, 32, and
64 bits
uintN_t: Similar to intN_t but unsigned
size_t: Used by the standard library containers everywhere a size or index
is needed, also result type of sizeof and alignof
uintptr_t: An integer type that is guaranteed to be able to hold all possible
values that result from a reinterpret_cast from any pointer
intptr_t: Similar to uintptr_t but signed
ptrdiff_t: Result type of expressions that subtract two pointers
max_align_t: Type which has alignment as least as large as all other scalar types
263
Dynamic Memory Management
264
Dynamic Memory Management Process Memory Layout
Each Linux process runs within its own virtual address space
• The kernel pretends that each process has access to a (huge) continuous
range of addresses (≈ 256 TiB on x86-64)
• Virtual addresses are mapped to physical addresses by the kernel using page
tables and the MMU (if available)
• Greatly simplifies memory management code in the kernel and improves
security due to memory isolation
• Allows for useful “tricks” such as memory-mapping files (more details later)
265
Dynamic Memory Management Process Memory Layout
266
Dynamic Memory Management Process Memory Layout
267
Dynamic Memory Management Process Memory Layout
Stack memory is typically used for objects with automatic storage duration
• The compiler can statically decide when allocations and deallocations must
happen
• The memory layout is known at compile-time
• Allows for highly optimized code (allocations and deallocations simply
increase/decrease a pointer)
268
Dynamic Memory Management Process Memory Layout
Example
foo.cpp foo.o
int foo() { foo():
int c = 2; pushq %rbp
int d = 21; movq %rsp, %rbp
movl $2, -4(%rbp)
return c * d; movl $21, -8(%rbp)
} movl -4(%rbp), %eax
imull -8(%rbp), %eax
int main() { popq %rbp
int a[100]; ret
int b = foo(); main:
pushq %rbp
return b; movq %rsp, %rbp
} subq $416, %rsp
call foo()
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
leave
ret
269
Dynamic Memory Management Process Memory Layout
Heap Segment
The heap is typically used for objects with dynamic storage duration
• The programmer must explicitly manage allocations and deallocations
• Allows much more flexible programs
Disadvantages
• Performance impact due to non-trivial implementation of heap-based memory
allocation
• Memory fragmentation
• Dynamic memory allocation is error-prone
• Memory leaks
• Double free (deallocation)
• Make use of debugging tools (GDB, ASAN (!))
270
Dynamic Memory Management Dynamic Memory Management in C++
Mechanisms give control over the storage duration and possibly lifetime of objects
• Level of control varies by method
• In all cases: Manual intervention required
271
Dynamic Memory Management Dynamic Memory Management in C++
Explanation
• Allocates heap storage for a single object or an array of objects
• Constructs and initializes a single object or an array of objects in the newly
allocated storage
• If initializer is absent, the object is default-initialized
• Returns a pointer to the object or the initial element of the array
272
Dynamic Memory Management Dynamic Memory Management in C++
Explanation
• If expression is nullptr nothing is done
• Invokes the destructor of the object that is being destroyed, or of every object
in the array that is being destroyed
• Deallocates the memory previously occupied by the object(s)
273
Dynamic Memory Management Dynamic Memory Management in C++
Node* first;
Node* last;
public:
~IntList() {
while (first != nullptr) {
Node* next = first->next;
delete first;
first = next;
}
}
void push_back(int i) {
Node* node = new Node{i, nullptr};
if (!last)
first = node;
else
last->next = node;
last = node;
}
};
274
Dynamic Memory Management Dynamic Memory Management in C++
Memory Leaks
if (condition)
return 42; // MEMORY LEAK
delete[] buffer;
return 123;
}
Avoid explicit memory management through new and delete whenever possible
275
Dynamic Memory Management Dynamic Memory Management in C++
Placement new
Constructs objects in already allocated storage
• Syntax: new (placement_params) type initializer
• placement_params must be a pointer to a region of storage large enough to
hold an object of type type
• The strict aliasing rule must not be violated
• Alignment must be ensured manually
• Only rarely required (e.g. for custom memory management)
#include <cstddef>
struct A { };
int main() {
std::byte* buffer = new std::byte[sizeof(A)];
A* a = new (buffer) A();
/* ... do something with a ... */
a->~A(); // we must explicitly call the destructor
delete[] buffer;
}
276
Dynamic Memory Management Dynamic Memory Management in C++
The lifetime of an object is equal to or nested within the lifetime of its storage
• Equal for regular new and delete
• Possibly nested for placement new
Example
struct A { };
int main() {
A* a1 = new A(); // lifetime of a1 begins, storage begins
a1.~A(); // lifetime of a1 ends
A* a2 = new (a1) A(); // lifetime of a2 begins
delete a2; // lifetime of a2 ends, storage ends
}
277
Dynamic Memory Management Memory Manipulation Primitives
std::memcpy (1)
278
Dynamic Memory Management Memory Manipulation Primitives
std::memcpy (2)
Example (straightforward copy)
#include <cstring>
#include <vector>
int main() {
std::vector<int> buffer = {1, 2, 3, 4};
buffer.resize(8);
std::memcpy(&buffer[4], &buffer[0], 4 * sizeof(int));
}
#include <cstring>
#include <cstdint>
int main() {
int64_t i = 42;
double j;
std::memcpy(&j, &i, sizeof(double)); // OK
}
279
Dynamic Memory Management Memory Manipulation Primitives
std::memmove (1)
280
Dynamic Memory Management Memory Manipulation Primitives
std::memmove (2)
#include <cstring>
#include <vector>
int main() {
std::vector<int> buffer = {1, 2, 3, 4};
buffer.resize(6);
std::memmove(&buffer[2], &buffer[0], 4 * sizeof(int));
// buffer is now {1, 2, 1, 2, 3, 4}
}
281
Copy and Move Semantics
282
Copy and Move Semantics Copy Semantics
Copy Semantics
283
Copy and Move Semantics Copy Semantics
284
Copy and Move Semantics Copy Semantics
class A {
private:
int v;
public:
explicit A(int v) : v(v) { }
A(const A& other) : v(other.v) { }
};
int main() {
A a1(42); // calls A(int)
285
Copy and Move Semantics Copy Semantics
Explanation
• Called whenever selected by overload resolution
• Returns a reference to the object itself (i.e. *this) to allow for chaining
assignments
286
Copy and Move Semantics Copy Semantics
class A {
private:
int v;
public:
explicit A(int v) : v(v) { }
A(const A& other) : v(other.v) { }
int main() {
A a1(42); // calls A(int)
A a2 = a1; // calls copy constructor
287
Copy and Move Semantics Copy Semantics
288
Copy and Move Semantics Copy Semantics
289
Copy and Move Semantics Copy Semantics
Implicit Definition
290
Copy and Move Semantics Copy Semantics
Example
struct A {
const int v;
int main() {
A a1(42);
291
Copy and Move Semantics Copy Semantics
292
Copy and Move Semantics Copy Semantics
#include <vector>
struct A {
int b;
double c;
};
int main() {
std::vector<A> buffer1;
buffer1.resize(10);
293
Copy and Move Semantics Copy Semantics
294
Copy and Move Semantics Copy Semantics
return *this;
}
};
295
Copy and Move Semantics Move Semantics
Move Semantics
296
Copy and Move Semantics Move Semantics
Typically called when an object is initialized from an rvalue reference of the same
type
• Syntax: class_name ( class_name&& ) noexcept
• class_name must be the name of the current class
• The noexcept keyword should be added to indicate that the constructor
never throws an exception
Explanation
• Overload resolution decides if the copy or move constructor of an object
should be called
• The std::move function in the <utility> header may be used to convert
an lvalue to an rvalue reference
• We know that the argument does not need its resources anymore, so we can
simply steal them
297
Copy and Move Semantics Move Semantics
struct A {
A(const A& other);
A(A&& other);
};
int main() {
A a1;
A a2(a1); // calls copy constructor
A a3(std::move(a1)); // calls move constructor
}
298
Copy and Move Semantics Move Semantics
299
Copy and Move Semantics Move Semantics
struct A {
A();
A(const A&);
A(A&&) noexcept;
int main() {
A a1;
A a2 = a1; // calls copy-constructor
A a3 = std::move(a1); // calls move-constructor
300
Copy and Move Semantics Move Semantics
The compiler will implicitly declare a public move constructor if all the following
conditions hold
• There are no user-declared copy constructors
• There are no user-declared copy assignment operators
• There are no user-declared move assignment operators
• There are no user-declared destructors
301
Copy and Move Semantics Move Semantics
Implicit Definition
303
Copy and Move Semantics Move Semantics
Example
struct A {
const int v;
int main() {
A a1(42);
304
Copy and Move Semantics Move Semantics
305
Copy and Move Semantics Move Semantics
306
Copy and Move Semantics Move Semantics
delete[] memory;
capacity = other.capacity;
memory = other.memory;
other.capacity = 0;
other.memory = nullptr;
return *this;
}
};
307
Copy and Move Semantics Move Semantics
Compilers must omit copy and move constructors under certain circumstances
• Objects are instead directly constructed in the storage into which they would
be copied/moved
• Results in zero-copy pass-by-value semantics
• Most importantly in return statements and variable initialization from a
temporary
• More optimizations allowed, but not required
308
Copy and Move Semantics Move Semantics
struct A {
int a;
A(int a) : a(a) {
std::cout << "constructed" << std::endl;
}
A foo() {
return A(42);
}
int main() {
A a = foo(); // prints only "constructed"
}
309
Copy and Move Semantics Move Semantics
Value Categories
cannot
move
• xvalues identify an object whose lvalue
resources can be reused
• prvalues compute the value of an
rvalue
move
xvalue prvalue
can
operand or initialize an object
glvalue
In particular, std::move just converts its argument to an xvalue expression
• std::move is exactly equivalent to a static_cast to an rvalue reference
• std::move is exclusively syntactic sugar (to guide overload resolution)
310
Copy and Move Semantics Idioms
Copy-And-Swap (1)
Implementation
• Exchange the resources between the argument and *this;
• Let the destructor clean up the resources of the argument
311
Copy and Move Semantics Idioms
Copy-And-Swap (2)
Example
#include <algorithm>
#include <cstring>
struct A {
unsigned capacity;
int* memory;
return *this;
} // destructor cleans up resources formerly held by *this
};
If a class requires one of the following, it almost certainly requires all three
• A user-defined destructor
• A user-defined copy constructor
• A user-defined copy assignment operator
Explanation
• Having a user-defined copy constructor usually implies some custom setup
logic which needs to be executed by copy assignment and vice-versa
• Having a user-defined destructor usually implies some custom cleanup logic
which needs to be executed by copy assignment and vice-versa
• The implicitly-defined versions are usually incorrect if a class manages a
resource of non-class type (e.g. a raw pointer, POSIX file descriptor, etc.)
313
Copy and Move Semantics Idioms
If a class follows the rule of three, move operations are defined as deleted
• If move semantics are desired for a class, it has to define all five special
member functions
• If only move semantics are desired for a class, it still has to define all five
special member functions, but define the copy operations as deleted
Explanation
• Not adhering to the rule of five usually does not lead to incorrect code
• However, many optimization opportunities may be inaccessible to the
compiler if no move operations are defined
314
Copy and Move Semantics Idioms
Bind the lifetime of a resource that has to be allocated to the lifetime of an object
• Resources can be allocated heap memory, sockets, files, mutexes, disk space,
database connections, etc.
• Guarantees availability of the resource during the lifetime of the object
• Guarantees that resources are released when the lifetime of the object ends
• Object should have automatic storage duration
• Known as the Resource Acquisition is Initialization (RAII) idiom
315
Copy and Move Semantics Idioms
Implementation of RAII
• Encapsulate each resource into a class whose sole responsibility is managing
the resource
• The constructor acquires the resource and establishes all class invariants
• The destructor releases the resource
• Typically, copy operations should be deleted and custom move operations
need to be implemented
316
Copy and Move Semantics Idioms
317
Copy and Move Semantics Idioms
if (condition)
return false; // no worries about forgetting to free memory
/* do something more */
int main() {
CustomIntBuffer buffer(5);
return foo(std::move(buffer));
}
318
Ownership
Ownership
319
Ownership
Ownership Semantics
The RAII idiom and move semantics together enable ownership semantics
• A resource should be “owned”, i.e. encapsulated, by exactly one C++ object
at all times
• Ownership can only be transferred explicitly by moving the respective object
• E.g., the CustomIntBuffer class implements ownership semantics for a
dynamically allocated int-array
320
Ownership Smart Pointers
std::unique_ptr (1)
321
Ownership Smart Pointers
std::unique_ptr (2)
Usage of std::unique_ptr (for details: see reference documentation)
Creation
• std::make_unique<type>(arg0, ..., argN), where arg0, ...,
argN are passed to the constructor of type
Conversion to bool
• std::unique_ptr is contextually convertible to bool, i.e. it can be used in
if statements in the same way as raw pointers
std::unique_ptr (3)
Example
#include <memory>
struct A {
int a;
int b;
323
Ownership Smart Pointers
std::unique_ptr (4)
int main() {
std::unique_ptr<int[]> buffer = foo(42);
/* do something */
}
324
Ownership Smart Pointers
std::shared_ptr (1)
Usage of std::shared_ptr
• Use std::make_shared for creation
• Remaining operations analogous to std::unique_ptr
• For details: See the reference documentation
325
Ownership Smart Pointers
std::shared_ptr (2)
Example
#include <memory>
#include <vector>
struct Node {
std::vector<std::shared_ptr<Node>> children;
int main() {
Node root;
root.addChild(std::make_shared<Node>());
root.addChild(std::make_shared<Node>());
root.children[0]->addChild(root.children[1]);
326
Ownership Smart Pointers
327
Ownership Smart Pointers
328
Ownership Smart Pointers
struct A { };
int main() {
A a;
readA(a);
readWriteA(a);
workOnCopyOfA(a);
consumeA(std::move(a)); // cannot call without std::move
}
329
Inheritance
Inheritance
330
Inheritance
Object-Oriented Programming
• Inheritance
• Implemented by class derivation in C++
• Derived Classes inherit the members of its base class(es)
• Covered in this lecture
331
Inheritance Basic Non-Polymorphic Inheritance
Any class type may be derived from one or more base classes
• Possible for both class and struct
• Base classes may in turn be derived from their own base classes
• Classes form an inheritance hierarchy
High-level Syntax
332
Inheritance Basic Non-Polymorphic Inheritance
Explanation
• access-specifier controls the inheritance mode (more details soon)
• access-specifier is optional; if present it can be one of the keywords
private, protected or public
• base-class-name is mandatory, it specifies the name of the class from
which to derive
• virtual-specifier is optional; if present it must be the keyword virtual
(only used for multiple inheritance)
333
Inheritance Basic Non-Polymorphic Inheritance
Examples
class Base {
int a;
};
334
Inheritance Basic Non-Polymorphic Inheritance
The initialization order is independent of any order in the member initializer list
335
Inheritance Basic Non-Polymorphic Inheritance
Derived(); Base::Base(int a)
Derived(int a, int b); : a(a) {
}; cout << "Base::Base(int)" << endl;
}
Derived::Derived() {
: b(42) {
cout << "Derived::Derived()" << endl;
}
Derived::Derived(int a, int b)
: Base(a), b(b) {
cout << "Derived::Derived(int, int)" << endl;
}
336
Inheritance Basic Non-Polymorphic Inheritance
int main() {
Derived derived0;
Derived derived1(123, 456);
}
$ ./foo
Base::Base()
Derived::Derived()
Base::Base(int)
Derived::Derived(int, int)
337
Inheritance Basic Non-Polymorphic Inheritance
Destructors (1)
The order in which the base class destructors are called is deterministic
• It depends on the order of construction, which in turn only depends on the
order of base classes in the base-specifier-list
338
Inheritance Basic Non-Polymorphic Inheritance
Destructors (2)
Derived::~Derived() {
cout << "Derived::~Derived()" << endl;
}
339
Inheritance Basic Non-Polymorphic Inheritance
Destructors (3)
int main() {
Derived derived;
}
$ ./foo
Derived::~Derived()
Base1::~Base1()
Base0::~Base0()
340
Inheritance Basic Non-Polymorphic Inheritance
Multiple inheritance can lead to additional problems even without reusing a name
• In a diamond-shaped inheritance hierarchy, members of the root class appear
twice in the most derived class
• Can be solved with virtual inheritance
• Should still be avoided whenever possible
341
Inheritance Basic Non-Polymorphic Inheritance
struct A {
void a();
};
struct B : A {
void a();
void b() {
a(); // calls B::a()
}
};
struct C : B {
void c() {
a(); // calls B::a()
}
};
342
Inheritance Basic Non-Polymorphic Inheritance
struct X {
void x();
};
struct B1 : X { };
struct B2 : X { };
struct D : B1, B2 {
void d() {
x(); // ERROR: x is present in B1 and B2
}
};
343
Inheritance Basic Non-Polymorphic Inheritance
struct A {
void a();
};
struct B : A {
void a();
};
int main() {
B b;
b.a(); // calls B::a()
b.A::a(); // calls A::a()
}
344
Inheritance Basic Non-Polymorphic Inheritance
Object Representation
The object representation of derived class objects accounts for inheritance
• The base class object is stored as a subobject in the derived class object
• Thus, derived classes may still be trivially constructible, copyable, or
destructible
foo.cpp foo.o
struct A { main:
int a = 42; pushq %rbp
int b = 123; movq %rsp, %rbp
}; movl $42, -12(%rbp)
movl $123, -8(%rbp)
struct B : A { movl $456, -4(%rbp)
int c = 456; movl $0, %eax
}; popq %rbp
ret
int main() {
B b;
}
345
Inheritance Polymorphic Inheritance
Polymorphic Inheritance
By default, inheritance in C++ is non-polymorphic
• Member definitions in a derived class can hide definitions in the base class
• For example, it matters if we call a function through a pointer to a base
object or a pointer to a derived object
#include <iostream>
struct Base {
void foo() { std::cout << "Base::foo()" << std::endl; }
};
int main() {
Derived d;
Base& b = d;
346
Inheritance Polymorphic Inheritance
347
Inheritance Polymorphic Inheritance
struct Base {
virtual void foo() { std::cout << "Base::foo()" << std::endl; }
};
struct Derived : Base {
void foo() { std::cout << "Derived::foo()" << std::endl; }
};
int main() {
Base b;
Derived d;
Base& br = b;
Base& dr = d;
348
Inheritance Polymorphic Inheritance
If these conditions are met, the function overrides the virtual base class function
• The derived function is also virtual and can be overridden by further-derived
classes
• The base class function does not need to be visible
If these conditions are not met, the function hides the virtual base class function
349
Inheritance Polymorphic Inheritance
struct Base {
private:
virtual void bar();
public:
virtual void foo();
};
int main() {
Derived d;
Base& b = d;
350
Inheritance Polymorphic Inheritance
A derived class can also inherit a function that overrides a virtual base class
function through multiple inheritance
• There must only be one final overrider at all times
• Multiple inheritance should be avoided anyway
351
Inheritance Polymorphic Inheritance
struct A {
virtual void foo();
virtual void bar();
virtual void baz();
};
struct B : A {
void foo();
void bar();
};
struct C : B {
void foo();
};
int main() {
C c;
A& cr = c;
352
Inheritance Polymorphic Inheritance
struct A {
virtual void foo();
virtual void bar();
virtual void baz();
};
struct B : A {
void foo();
void bar();
};
struct C : B {
void foo();
};
int main() {
B b;
A& br = b;
353
Inheritance Polymorphic Inheritance
The overriding and base class functions can have covariant return types
• Both types must be single-level pointers or references to classes
• The referenced/pointed-to class in the base class function must be a direct or
indirect base class of the referenced/pointed-to class in the derived class
function
• The return type in the derived class function must be at most as cv-qualified
as the return type in the base class function
• Most of the time, the referenced/pointed-to class in the derived class
function is the derived class itself
354
Inheritance Polymorphic Inheritance
Example
struct Base {
virtual Base* foo();
virtual Base* bar();
};
355
Inheritance Polymorphic Inheritance
struct Base {
Base() { foo(); }
virtual void foo();
};
int main() {
Derived d; // On construction, Base::foo() is called
}
356
Inheritance Polymorphic Inheritance
Virtual Destructors
Derived objects can be deleted through a pointer to the base class
• Undefined behavior unless the destructor in the base class is virtual
• The destructor in a base class should either be protected and non-virtual or
public and virtual
#include <memory>
struct Base {
virtual ~Base() { };
};
int main() {
Base* b = new Derived();
delete b; // OK
}
357
Inheritance Polymorphic Inheritance
struct Base {
virtual void foo(int i);
virtual void bar();
};
358
Inheritance Polymorphic Inheritance
struct Base {
virtual void foo() final;
};
359
Inheritance Polymorphic Inheritance
360
Inheritance Polymorphic Inheritance
C++ allows abstract classes which cannot be instantiated, but used as a base class
• Any class which declares or inherits at least one pure virtual function is an
abstract class
• A pure virtual member function declaration contains the sequence = 0 after
the declarator and override/final specifiers
• Pointers and references to an abstract class can be declared
361
Inheritance Polymorphic Inheritance
Example
struct Base {
virtual void foo() = 0;
};
int main() {
Base b; // ERROR
Derived d;
Base& dr = d;
dr.foo(); // calls Derived::foo()
}
362
Inheritance Polymorphic Inheritance
struct Base {
virtual void foo() = 0;
};
363
Inheritance Polymorphic Inheritance
struct Base {
virtual ~Base() = 0;
};
Base::~Base() { }
int main() {
Base b; // ERROR
}
364
Inheritance Polymorphic Inheritance
struct Base {
virtual ~Base();
virtual void foo() = 0;
};
int main() {
std::unique_ptr<Base> b = std::make_unique<Derived>();
b->foo(); // calls Derived::foo()
365
Inheritance Conversions
dynamic_cast (1)
366
Inheritance Conversions
dynamic_cast (2)
Example
struct A {
virtual ~A() = 0;
};
struct B : A {
void foo() const;
};
struct C : A {
void bar() const;
};
367
Inheritance Conversions
dynamic_cast (3)
dynamic_cast has a non-trivial performance overhead
• Notable impact if many casts have to be performed
• Alternative: Use a type enum in conjunction with static_cast
struct Base {
enum class Type {
Base,
Derived
};
Type type;
Base() : type(Type::Base) { }
Base(Type type) : type(type) { }
virtual ~Base();
};
368
Inheritance Conversions
dynamic_cast (4)
Example (continued)
break;
}
}
369
Inheritance Implementation of Polymorphic Inheritance
Vtables (1)
370
Inheritance Implementation of Polymorphic Inheritance
Vtables (2)
Example
struct Base {
Code Segment
virtual void foo(); Base::foo(): vtable for Base:
virtual void bar();
}; Instructions... Base::foo()
Base::bar(): Base::bar()
struct Derived : Base {
void foo() override; Instructions... vtable for Derived:
};
Derived::foo(): Derived::foo()
int main() { Instructions... Base::bar()
Base b;
Derived d;
Stack
Base& br = b; Derived d:
Base& dr = d;
vtable pointer
br.foo(); Base b:
dr.foo();
} vtable pointer
371
Inheritance Implementation of Polymorphic Inheritance
Performance Implications
372
Inheritance Inheritance Modes
Inheritance Modes
373
Inheritance Inheritance Modes
Semantics
• Public base class members are usable as public members of the derived class
• Protected base class members are usable as protected members of the
derived class
374
Inheritance Inheritance Modes
class A {
protected:
int a;
public:
int b;
};
class B : public A {
public:
void foo() {
return a + 42; // OK: a is usable as protected member of B
}
};
int main() {
B b;
b.b = 42; // OK: b is usable as public member of B
b.a = 42; // ERROR: a is not visible
}
375
Inheritance Inheritance Modes
Semantics
• Public base class members are usable as private members of the derived class
• Protected base class members are usable as private members of the derived
class
376
Inheritance Inheritance Modes
Example
class A {
protected:
A(int); // Constructor is protected for some reason
};
class C : private A {
public:
C() : A(42) { }
377
Inheritance Inheritance Modes
Semantics
• Public base class members are usable as protected members of the derived
class
• Protected base class members are usable as protected members of the
derived class
• Within the derived class and all further-derived classes, pointers and
references to a derived object may be used where a pointer or reference to
the base object is expected
378
Inheritance Inheritance Modes
class A {
protected:
int a;
public:
int b;
};
class B : protected A {
public:
void foo() {
return a + 42; // OK: a is usable as protected member of B
}
};
int main() {
B b;
b.b = 42; // ERROR: b is not visible
b.a = 42; // ERROR: a is not visible
}
379
Inheritance Multiple Inheritance
Multiple Inheritance
380
Inheritance Exceptions
Exceptions in C++
While transferring control up the call stack, C++ performs stack unwinding
• Properly cleans up all objects with automatic storage duration
• Ensures correct behavior e.g. of RAII classes
381
Inheritance Exceptions
Throwing Exceptions
#include <exception>
void foo(unsigned i) {
if (i == 42)
throw 42;
throw std::exception();
}
382
Inheritance Exceptions
Handling Exceptions
Exceptions are handled in try-catch blocks
• Exceptions that occur while executing the try-block can be handled in the
catch-blocks
• The parameter type of the catch-block determines which type of exception
causes the block to be entered
#include <exception>
void bar() {
try {
foo(42);
} catch (int i) {
/* handle exception */
} catch (const std::exception& e) {
/* handle exception */
}
}
383
Inheritance Exceptions
Usage Guidelines
384
Templates
Templates
385
Templates
Motivation
386
Templates Basic Templates
Templates
387
Templates Basic Templates
Example
class A;
//------------------------------------------------------------
template<class T> // T is a type template parameter
class vector {
public:
/* ... */
void push_back(T&& element);
/* ... */
};
//------------------------------------------------------------
int main() {
vector<int> vectorOfInt; // int is substituted for T
vector<A> vectorOfA; // A is substituted for T
}
388
Templates Basic Templates
Template Syntax
389
Templates Basic Templates
390
Templates Basic Templates
public:
T& operator[](unsigned i) {
assert(i < N);
return storage[i];
}
};
391
Templates Basic Templates
392
Templates Basic Templates
393
Templates Basic Templates
Using Templates
394
Templates Basic Templates
Template arguments for type template parameters must name a type (which may
be incomplete)
class A;
//------------------------------------------------------------
template<class T1, class T2 = int, class T3 = double>
class Foo { };
//------------------------------------------------------------
int main() {
Foo<int> foo1;
Foo<A> foo2;
Foo<A*> foo3;
Foo<int, A> foo4;
Foo<int, A, A> foo5;
}
395
Templates Basic Templates
396
Templates Basic Templates
Example
//------------------------------------------------------------
template <unsigned N>
class Foo { };
//------------------------------------------------------------
int main() {
Foo<42u> foo1; // OK: no conversion
Foo<42> foo2; // OK: numeric conversion
}
397
Templates Basic Templates
constexpr
#include <array>
//--------------------------------------------------------------
class Element;
//--------------------------------------------------------------
class Foo {
constexpr unsigned numElements = 42;
constexpr unsigned calculateBufferSize(unsigned elements) {
return elements * sizeof(Element);
}
398
Templates Basic Templates
#include <array>
//--------------------------------------------------------------
template <class T, unsigned long N>
class MyArray { };
//--------------------------------------------------------------
template <template<class, unsigned long> class Array>
class Foo {
Array<int, 42> bar;
};
//--------------------------------------------------------------
int main() {
Foo<MyArray> foo1;
Foo<std::array> foo2;
}
399
Templates Basic Templates
public:
/* ... */
/* ... */
};
400
Templates Basic Templates
class A { };
//--------------------------------------------------------------
template <class T>
void swap(T& a, T& b) {
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
//--------------------------------------------------------------
int main() {
A a1;
A a2;
swap<A>(a1, a2);
swap(a1, a2); // Also OK: Template arguments are deduced
}
401
Templates Basic Templates
namespace something::extremely::nested {
//--------------------------------------------------------------
template <class T, class R>
class Handle { };
//--------------------------------------------------------------
} // namespace something::extremely::nested
//--------------------------------------------------------------
template <typename T>
using Handle = something::extremely::nested::Handle<T, void*>;
//--------------------------------------------------------------
int main() {
Handle<int> handle1;
Handle<double> handle2;
}
402
Templates Basic Templates
403
Templates Basic Templates
Foo foo;
foo.printSize<Foo::ArrayType<int>>();
}
404
Templates Basic Templates
Template Instantiation
A function or class template by itself is not a type, an object, or any other entity
• No assembly is generated from a file that contains only template definitions
• A template specialization must be instantiated for any assembly to appear
Template instantiation
• Compiler generates an actual function or class for a template specialization
• Explicit instantiation: Explicitly request instantiation of a specific
specialization
• Implicit instantiation: Use a template specialization in a context that requires
a complete type
405
Templates Basic Templates
Explanation
• Explicit instantiations have to follow the one definition rule
• Generates assembly for the function specialization or class specialization and
all its member functions
• Template definition must be visible at the point of explicit instantiation
406
Templates Basic Templates
407
Templates Basic Templates
408
Templates Basic Templates
T bar();
};
//--------------------------------------------------------------
int main() {
A<int> a; // Instantiates only A<int>
int x = a.foo(32); // Instantiates A<int>::foo
409
Templates Basic Templates
Implicit instantiation
• Pro: Template can be used with any suitable type
• Pro: No unnecessary assembly is generated
• Con: Definition has to be provided in header
• Con: User of our templates has to compile them
Explicit instantiation
• Pro: Explicit instantiations can be compiled into library
• Pro: Definition can be encapsulated in source file
• Con: Limits usability of our templates
410
Templates Basic Templates
Instantiation Caveats
411
Templates Basic Templates
A(T value);
412
Templates Basic Templates
413
Templates Basic Templates
Example: std::vector<T>::push_back
• In order to use void push_back(const T& value);, T must meet the
requirements of CopyInsertable
• In order to use void push_back(T&& value);, T must meet the
requirements of MoveInsertable
414
Templates Basic Templates
B* b; // B refers to A<T>::B
void foo();
void bar() {
foo(); // foo refers to A<T>::foo
}
};
415
Templates Basic Templates
Names that are members of templates are not considered to be types by default
• When using a name that is a member of a template outside of any template
declaration or definition
• When using a name that is not a member of the current instantiation within
a template declaration or definition
• If such a name should be considered as a type, the typename disambiguator
has to be used
416
Templates Basic Templates
Example
struct A {
using MemberTypeAlias = float;
};
//--------------------------------------------------------------
template <class T>
struct B {
using AnotherMemberTypeAlias = typename T::MemberTypeAlias;
};
//--------------------------------------------------------------
int main() {
// value has type float
B<A>::AnotherMemberTypeAlias value = 42.0f;
}
417
Templates Basic Templates
418
Templates Basic Templates
Reference Collapsing
int main() {
Foo<int&&>::Trref x; // what is the type of x?
}
419
Templates Template Specialization
We may want to modify the behavior of templates for specific template arguments
• For example, a templated find method can employ different algorithms on
arrays (binary search) vs. linked lists (linear search)
420
Templates Template Specialization
Full Specialization
Defines a specific implementation for a full set of template arguments
• Has to appear after the declaration of the original template
• Syntax: template <> declaration
• Most types of templates can be fully specialized
421
Templates Template Specialization
Partial Specialization
Defines a specific implementation for a partial set of template arguments
• Has to appear after the declaration of the original template
• template < parameter-list > class name < argument-list >
• template < parameter-list > struct name < argument-list >
• Only class templates can be partially specialized
• Function overloads can simulate function template specialization
template <class C, class T>
class SearchAlgorithm {
void find (const C& container, const T& value) {
/* do linear search */
}
};
//--------------------------------------------------------------
template<class T>
class SearchAlgorithm<std::vector<T>, T> {
void find (const std::vector<T>& container, const T& value) {
/* do binary search */
}
};
422
Templates Template Argument Deduction
423
Templates Template Argument Deduction
424
Templates Template Argument Deduction
#include <memory>
//-----------------------------------------------------------
template <class T>
struct Foo {
Foo(T t);
};
//-----------------------------------------------------------
int main() {
Foo foo(12);
std::unique_ptr ptr = make_unique<int>(42);
}
425
Templates Placeholder Type Specifiers
#include <unordered_map>
//-----------------------------------------------------------
int main() {
std::unordered_map<int, const char*> intToStringMap;
426
Templates Placeholder Type Specifiers
// GOOD:
const auto** f4 = foo(); // auto is int
}
427
Templates Placeholder Type Specifiers
struct A {
const A& foo() { return *this; }
};
//-----------------------------------------------------------
int main() {
A a;
auto a1 = a.foo(); // BAD: auto is const A, copy
const auto& a2 = a.foo() // GOOD: auto is A, no copy
}
428
Templates Placeholder Type Specifiers
Explanation
• The identifiers in identifier-list are bound to the subobjects or
elements of the initializer
• Can bind to arrays, tuple-like types and accessible data members
• Very useful during iteration, especially over associative containers
429
Templates Placeholder Type Specifiers
Example
#include <utility>
//-----------------------------------------------------------
struct Foo {
float y;
long z;
};
//-----------------------------------------------------------
std::pair<int, long> bar();
//-----------------------------------------------------------
int main() {
Foo foo;
int array[4];
auto [a1, a2, a3, a4] = array; // copies array, a1 - a4 refer to copy
auto& [y, z] = foo; // y refers to foo.y, z refers to foo.z
auto [l, r] = bar(); // move-constructs pair p, l refers to p.first,
// r refers to p.second
}
430
Templates Variadic Templates
Parameter packs are template parameters that accept zero or more arguments
• Non-type: type ... Args
• Type: typename|class ... Args
• Template: template < parameter-list > typename|class ... Args
• Can appear in alias, class and function template parameter lists
• Templates with at least on parameter pack are called variadic templates
431
Templates Variadic Templates
printTuple(tuple);
printElements(1, 2, 3, 4);
}
432
Templates Variadic Templates
#include <iostream>
//-----------------------------------------------------------
void printElements() { }
//-----------------------------------------------------------
template <typename Head, typename... Tail>
void printElements(const Head& head, const Tail&... tail) {
std::cout << head;
printElements(tail...);
}
//-----------------------------------------------------------
int main() {
printElements(1, 2, 3.0, 3.14, 4);
}
433
Templates Variadic Templates
Semantics
• (E ◦ . . .) becomes E1 ◦ (. . . (En−1 ◦ En ))
• (. . . ◦ E ) becomes ((E1 ◦ E2 ) ◦ . . .) ◦ En
• (E ◦ . . . ◦ I) becomes E1 ◦ (. . . (En−1 ◦ (En ◦ I)))
• (I ◦ . . . ◦ E ) becomes (((I ◦ E1 ) ◦ E2 ) ◦ . . .) ◦ En
434
Templates Variadic Templates
435
Templates Template Metaprogramming
Template Metaprogramming
436
Templates Idioms
static_assert
437
Templates Idioms
Type Traits
Type traits compute information about types at compile time
• Simple form of template metaprogramming
• E.g. std::numeric_limits is a type trait
Standard Library I
439
Standard Library I Introduction
440
Standard Library I Introduction
• I/O
• Input-/Output streams
• File streams
• String streams
• Threads
• Thread class
• (shared) mutexes
• futures
• And much more
• Localization
• Regex
• Atomics
• Filesystem support
• …
442
Standard Library I Strings
std::string
443
Standard Library I Strings
Creating a std::string
The default constructor creates an empty string of length 0
std::string s;
s.size(); // == 0
444
Standard Library I Strings
Since both functions return a reference, this can be used to modify the string
std::string s = "Hello World!";
s.at(4) = 'x';
s[6] = 'Y';
s[10] = s.at(9);
std::cout << s << std::endl; // prints "Hellx Yorll!"
Out of bounds access with array notation results in undefined behaviour, at()
throws an exception
445
Standard Library I Strings
446
Standard Library I Strings
Comparing std::string
The std::string class provides a compare() function, comparing two strings
(or substrings) lexicographically
std::string s1 = "Hello World!";
std::string s2 = "Goodbye World!";
447
Standard Library I Strings
std::string Operations
The standard library provides features a modern string library is expected to have,
such as:
• size() or length(): The number of characters in the string
• empty(): Returns true if the string has no characters
• append() and +=: Appends another string or character. No need for manual
memory allocations!
• + concatenates two strings
• find(): Returns the offset of thie first occurence of the substring, or the
constant std::string::npos if not found
• substr(): Returns a new std::string that is a substring at the given
offset and length. Be careful! Most of the times, you probably want a string
view instead of a substring!
448
Standard Library I Strings
std::string_view (1)
std::string_view (2)
Example
std::string s = "garbage garbage garbage interesting garbage";
// Or in place:
s_view.remove_prefix(24); // O(1)
s_view.remove_suffix(s_view.size() - 11); // O(1)
String Literals
451
Standard Library I Optional, Pair, Tuple
std::optional
std::optional is a class encapsulating a value that might or might not exist
• Defined in the header <optional>
• Some functions might fail or return without a valid result (e.g. looking up a
non-existing file)
• It’s unfavourable to encode such failures with a value of the function domain
(e.g. an empty string when file could not be read)
• std::optional helps to express such results:
• At any point in time, an optional either has a value, or it doesn’t
• If the computation succeeded, it returns an optional containing a value
• If it failed, it returns an optional without a value
• The template parameter T denotes, of which type the optional may contain a
value (e.g. optional<int> might contain an int)
• Guarantees to not dynamically allocate any memory when being assigned a
value
• Is an object, despite supporting the dereference operators * and ->
• Internally implemented as an object with a member of type T and a boolean
452
Standard Library I Optional, Pair, Tuple
std::optional: Creation
Optionals are created through its constructor or with std::make_optional:
std::optional<std::string> might_fail(int arg) {
if (arg == 0) {
return std::optional<std::string>("zero");
} else if (arg == 1) {
return "one"; // equivalent to the case above
} else if (arg < 7) {
//std::make_optional takes constructor arguments of type T
return std::make_optional<std::string>("less than 7");
} else {
return std::nullopt; // alternatively: return {}
}
}
The value of an optional can be read whith value() (throws exception when
empty) or dereferenced with * or -> (undefined behavior when empty)
might_fail(3).value(); // "less than 7"
might_fail(8).value(); // throws std::bad_optional_access
// Or even simpler:
std::optional<std::string> opt5 = might_fail(5)
if (opt5) { //contextual conversion to bool
opt5->size(); // 11
}
Clearing an optional:
std::optional<std::string> opt5 = might_fail(5)
opt5.has_value(); // true
opt5.reset(); // Clears the value
opt5.has_value(); // false
454
Standard Library I Optional, Pair, Tuple
std::pair
std::pair<T, U> is a template class that stores exactly one object of type T
and one of type U.
• Defined in the header <utility>
• Constructor takes object of T and U
• Pairs can also be constructed with std::make_pair()
• Objects can be accessed with first and second
• Can be compared for equality and inequality
• Can be compared lexicographically with <, <=, >, and >=
455
Standard Library I Optional, Pair, Tuple
std::tuple
456
Standard Library I Optional, Pair, Tuple
std::tie()
Tuples can also contain values of reference type. They can be constructed with
std::tie().
• Can be used to easily “decompose” a tuple into existing variables
• Can also be used to quickly do lexicographic comparison on different objects
457
Standard Library I Optional, Pair, Tuple
458
Standard Library I Containers
459
Standard Library I Containers
std::vector
460
Standard Library I Containers
Access elements via C-style array notation, via at(), or through a raw pointer:
fib.at(0) // == 1;
fib[1] // == 1;
Update elements via C-style array notation, via at(), or through a raw pointer:
fib[3] = 43;
fib.at(2) = 42;
fib.data()[1] = 41; // fib is now 1, 41, 42, 43
Note: It is not possible to insert new elements this way! You can only update
existing ones.
461
Standard Library I Containers
462
Standard Library I Containers
std::vector<ExpensiveToCopy> vec;
463
Standard Library I Containers
If the vector won’t grow in the future, reduce its capacity to save unused space:
std::vector<int> vec;
vec.reserve(100); // Reserve some space to be sure
// Free the space for the other 90 elements we reserved but didn't need
vec.shrink_to_fit();
464
Standard Library I Containers
std::unordered_map
465
Standard Library I Containers
Lookup the value to a key with C-style array notation, or with at():
name_to_grade["huber"]; // == 2.7
name_to_grade.at("schmidt"); // == 5.0
if (search != name_to_grade.end()) {
// Returns an iterator pointing to a pair!
search->first; // == "schmidt"
search->second; // == 5.0
}
In contrast to vectors, the array-notation also allows the insertion of new KV-pairs!
Maps also allow the direct insertion of pairs:
std::pair<std::string, double> pair("mueller", 1.0);
name_to_grade.insert(pair);
// Or simpler:
name_to_grade.insert({"mustermann", 3.7});
467
Standard Library I Containers
std::map
In contrast to unordered maps, the keys of std::map are sorted
• Defined in the header <map>
• Interface largely the same to std::unordered_map
• Optionally accepts a custom comparison function as template parameter
• Is internally a tree (usually AVL- or R/B-Tree)
• O(log n) complexity for random access, search, insertion, and removal
std:map also allows to search for ranges:
upper_bound() returns an iterator pointing to the first greater element:
std::map<int, int> x_to_y = {{1, 1}, {3, 9}, {7, 49}};
auto gt3 = x_to_y.upper_bound(3);
std::unordered_set
469
Standard Library I Containers
if (search != shopping_list.end()) {
// Returns an iterator pointing to the element!
*search; // == "milk"
}
470
Standard Library I Containers
result = shopping_list.insert("broccoli");
result.second; // true, "broccoli" was added
*result.first; // "broccoli", iterator points to newly inserted element
471
Standard Library I Containers
std::set
In contrast to unordered sets, the elements of std::set are sorted
• Defined in the header <set>
• Interface largely the same to std::unordered_set
• Optionally accepts a custom comparison function as template parameter
• Is internally a tree (usually AVL- or R/B-Tree)
• O(log n) complexity for random access, search, insertion, and removal
std:set also allows to search for ranges:
upper_bound() returns an iterator pointing to the first greater element:
std::set<int> x = {1, 3, 7};
auto gt3 = x.upper_bound(3);
Be careful: When writing to a container, all existing iterators are invalidated and
can no longer be used (some exceptions apply)!
474
Standard Library I Iterators
The end iterator points to the first element after the container. Dereferencing it
results in undefined behavior:
*end; // undefined behavior
An iterator can be incremented (just like a pointer) to point at the next element:
++it; // Prefer to use pre-increment
std::cout << *it; // prints "two"
475
Standard Library I Iterators
Such a loop requires the range expression (here: vec) to have a begin() and
end() member.
vec.begin() is assigned to an internal iterator which is dereferenced, assigned to
the range declaration (here: auto elem), and then incremented until it equals
vec.end().
476
Standard Library I Iterators
Input- and OutputIterator are the most basic iterators. They have the following
features:
• Equality comparison: Checks if two iterators point to the same position
• Dereferencable with the * and -> operators
• Incrementable, to point at the next element in sequence
• A dereferenced InputIterator can only by read
• A dereferenced OutputIterator can only be written to
478
Standard Library I Iterators
479
Standard Library I Iterators
ContiguousIterator
• Introduced with C++17
• Guarantees that elements are stored in memory contiguously
• Formally: For every iterator it and integral value n: if it + n is a valid
iterator, then ∗(it + n) ⇔ ∗(std::addressof(∗it) + n)
• Orthogonal to all other iterators (i.e. a ContiguousIterator is not
necessarily a RandomAccessIterator)
• Code predating C++17 often treats RandomAccessIterators of
std::string, std::vector, and std::array as if they were
ContiguousIterators
480
Standard Library I Streams and I/O
All streams are subclasses of std::basic_ios and have the following member
functions:
• good(), fail(), bad(): Checks if the stream is in a specific error state
• eof(): Checks if the stream has reached end-of-file
• operator bool(): Returns true if stream has no errors
int value;
if (std::cin >> value) {
std::cout << "value = " << value << std::endl;
} else {
std::cout << "error" << std::endl;
}
482
Standard Library I Streams and I/O
Input Streams
Input streams (std::istream) support several input functions:
• operator>>(): Reads a value of a given type from the stream, skips leading
whitespace
• operator>>() can be overloaded for own types as second argument to
support being read from a stream
• get(): Reads single or multiple characters until a delimiter is found
• read(): Reads given number of characters
483
Standard Library I Streams and I/O
Output Streams
484
Standard Library I Streams and I/O
String Streams
std::stringstream can be used when input and output should be written and
read from a std::string.
• Defined in the header <sstream>
• Is a subclass of std::istream and std::ostream
• Initial contents can be given in the constructor
• Contents can be extracted and set with str()
485
Standard Library I Streams and I/O
File Streams
The standard library defines several streams for file I/O in the <fstream> header:
• std::ifstream: Input file stream to read to a file
• std::ofstream: Output file stream to write to a file
• std::fstream: File stream to read and write to a file
std::ifstream input("input_file");
if (!input) { std::cout << "couldn't open input_file\n"; }
std::ofstream output("output_file");
if (!output) { std::cout << "couldn't open output_file\n"; }
// Read an int from input_file and write it to output_file
int value = -1;
if (!(input >> value)) {
std::cout << "couldn't read from file\n";
}
if (!(output << value)) {
std::cout << "couldn't write to file\n";
}
486
Standard Library I Streams and I/O
Disadvantage of Streams
Even though streams are nice to use, they should be avoided in many cases:
• Streams make have use of virtual functions and virtual inheritance which by
itself can sometimes be a significant performance overhead
• Streams respect the system’s locale settings (e.g. whether to use a period or
a comma for floating point numbers) which also makes them slow
• Especially parsing of integers is very inefficient
General rule: When input is typed in by a user, using streams is fine. When input
is read from files or generated automatically, better use OS-specific functions.
487
Standard Library II
Standard Library II
488
Standard Library II Function Objects
489
Standard Library II Function Objects
490
Standard Library II Function Objects
491
Standard Library II Function Objects
492
Standard Library II Function Objects
493
Standard Library II Function Objects
494
Standard Library II Function Objects
Example
495
Standard Library II Function Objects
496
Standard Library II Function Objects
497
Standard Library II Function Objects
498
Standard Library II Function Objects
Capture types
int main() {
int i = 0;
int j = 42;
499
Standard Library II Function Objects
int main() {
int i = 42;
i = 0;
int a = lambda1(); // a = 84
int b = lambda2(); // b = 42
}
500
Standard Library II Function Objects
struct Foo {
int i = 0;
void bar() {
auto lambda1 = [*this]() {return i + 42; };
auto lambda2 = [this](){ return i + 42; };
i = 42;
int a = lambda1(); // a = 42
int b = lambda2(); // b = 84
}
};
501
Standard Library II Function Objects
struct Foo {
int i = 0;
void bar() {
auto lambda1 = [&]() {return i + 42; };
auto lambda2 = [=](){ return i + 42; };
i = 42;
int a = lambda1(); // a = 84
int b = lambda2(); // b = 84
}
};
502
Standard Library II Function Objects
#include <memory>
int main() {
auto ptr = std::make_unique<int>(4);
int a = f2(); // 4
ptr.reset();
int b = f2(); // undefined behavior
}
503
Standard Library II Function Objects
Situation so far
• Functions are generally stateless
• State has to be kept in surrounding object, e.g. class instances
• Lambda expressions allow limited state-keeping
504
Standard Library II Function Objects
struct Adder {
int value;
505
Standard Library II Function Objects
std::function (1)
#include <functional>
//--------------------------------------------------------
int add2(int p){ return p + 2; }
//--------------------------------------------------------
int main() {
std::function<int(int)>adder = add2;
int a = adder(5); // a = 7
}
506
Standard Library II Function Objects
std::function (2)
#include <functional>
//--------------------------------------------------------
std::function<int()> getFunction(bool first){
int a = 14;
if (first)
return [=]() { return a; };
else
return [=]() { return 2 * a; };
}
//--------------------------------------------------------
int main() {
return getFunction(false)() + getFunction(true)(); // 42
}
507
Standard Library II Function Objects
508
Standard Library II The Algorithms Library
509
Standard Library II The Algorithms Library
std::sort
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<unsigned> v = {3, 4, 1, 2};
std::sort(v.begin(), v.end()); // 1, 2, 3, 4
}
510
Standard Library II The Algorithms Library
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<unsigned> v = {3, 4, 1, 2};
std::sort(v.begin(), v.end(), [](unsigned lhs, unsigned rhs) {
return lhs > rhs;
}); // 4, 3, 2, 1
}
511
Standard Library II The Algorithms Library
512
Standard Library II The Algorithms Library
Searching
General semantics
• Search operations return iterators pointing to the result
• Unsuccessful operations are usually indicated by returning the last iterator
of a range [first, last)
513
Standard Library II The Algorithms Library
Searching - Unsorted
514
Standard Library II The Algorithms Library
std::find
Example
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {2, 6, 1, 7, 3, 7};
515
Standard Library II The Algorithms Library
std::find_if
Example
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {2, 6, 1, 7, 3, 7};
assert(res2 == vec.end());
}
516
Standard Library II The Algorithms Library
Searching - Sorted
517
Standard Library II The Algorithms Library
std::binary_search
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};
518
Standard Library II The Algorithms Library
std::lower_bound
Returns iterator pointing to the first element >= the search value
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};
519
Standard Library II The Algorithms Library
std::upper_bound
Returns iterator pointing to the first element > the search value
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};
520
Standard Library II The Algorithms Library
std::equal_range
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};
521
Standard Library II The Algorithms Library
Permutations
522
Standard Library II The Algorithms Library
std::iota
#include <numeric>
#include <memory>
//--------------------------------------------------------
int main() {
auto heapArray = std::make_unique<int[]>(5);
std::iota(heapArray.get(), heapArray.get() + 5, 2);
523
Standard Library II The Algorithms Library
std::next_permutation
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {1, 2, 3};
524
Standard Library II The Algorithms Library
std::prev_permutation
#include <algorithm>
#include <vector>
//--------------------------------------------------------
int main() {
std::vector<int> v = {1, 3, 2};
525
Standard Library II The Algorithms Library
Additional Functionality
526
Standard Library II The Random Library
527
Standard Library II The Random Library
528
Standard Library II The Random Library
#include <cstdint>
#include <random>
//--------------------------------------------------------
int main() {
std::mt19937 engine(42);
529
Standard Library II The Random Library
std::random_device
#include <cstdint>
#include <random>
//--------------------------------------------------------
int main() {
std::mt19937 engine(std::random_device()());
530
Standard Library II The Random Library
Distributions
531
Standard Library II The Random Library
std::uniform_int_distribution
Generates discrete uniform random numbers in range [a, b]
• Integer type specified as template parameter
• Constructed as uniform_int_distribution<T>(T a, T b)
• If not specified a defaults to 0 and b to the maximum value of T
• Numbers generated by operator(Generator& g)() where g is any random
number generator
#include <random>
//--------------------------------------------------------
int main() {
std::mt19937 engine(42);
std::uniform_int_distribution<int> dist(-2, 2);
int d1 = dist(engine); // d1 == -1
int d2 = dist(engine); // d2 == -2
}
532
Standard Library II The Random Library
std::uniform_real_distribution
Generates continuous uniform random numbers in range [a, b]
• Floating point type specified as template parameter
• Constructed as uniform_real_distribution<T>(T a, T b)
• If not specified a defaults to 0 and b to the maximum value of T
• Numbers generated by operator(Generator& g)() where g is any random
number generator
#include <random>
//--------------------------------------------------------
int main() {
std::mt19937 engine(42);
std::uniform_real_distribution<float> dist(-2, 2);
533
Standard Library II The Random Library
Seeding
534
Standard Library II The Random Library
Example
#include <random>
//--------------------------------------------------------
int main() {
// Use random device to seed generator
std::random_device rd;
// Use pseudo-random generator to get random numbers
std::mt19937 engine(rd());
// Use distribution to generate dice rolls
std::uniform_int_distribution<> dist(1, 6);
535
Standard Library II The Random Library
int randomDiceroll() {
return gen() % 6 + 1;
}
536
Multi-Threading
Multi-Threading
537
Multi-Threading
Multi-Threading in C++
In C++ it is allowed to run multiple threads simultaneously that use the same
memory.
• Multiple threads may read from the same memory location
• All other accesses (i.e. read-write, write-read, write-write) are called conflicts
• Conflicting operations are only allowed when threads are synchronized
• This can be done with mutexes or atomic operations
• Unsynchronized accesses (also called data races), deadlocks, and other
potential issues when using threads are undefined behavior!
538
Multi-Threading Threads Library
The header <thread> defines the class std::thread that can be used to start
new threads.
• Using this class is the best way to use threads platform-independently
• May require additional compiler flags: -pthread for gcc and clang
539
Multi-Threading Threads Library
The member function join() can be used to wait for a thread to finish.
• join() must be called exactly once for each thread
• When the destructor of an std::thread is called, the program is terminated
if it has an associated thread that was not joined
540
Multi-Threading Threads Library
std::threads are not copyable, but movable, so they can be used in containers.
Moving an std::thread transfers all resources associated with the running
thread. Only the moved-to thread can be joined.
std::vector<std::thread> threadPool;
for (int i = 1; i <= 9; ++i) {
threadPool.emplace_back([i] { safe_print(i); });
}
// Digits 1 to 9 are printed (unordered)
for (auto& t : threadPool) {
t.join();
}
541
Multi-Threading Threads Library
The thread library also contains other useful functions that are closely related to
starting and stopping threads:
• std::this_thread::sleep_for(): Stop the current thread for a given
amount of time
• std::this_thread::sleep_until(): Stop the current thread until a
given point in time
• std::this_thread::yield(): Let the operating system schedule another
thread
• std::this_thread::get_id(): Get the (operating-system-specific) id of
the current thread
542
Multi-Threading Mutual Exclusion
Mutual Exclusion
Note: Mutexes are usually inefficient as they are used very coarse-grained and
sometimes require communication with the operating system.
543
Multi-Threading Mutual Exclusion
Mutexes
A mutex is the most basic synchronization primitive which can be locked and
unlocked by exactly one thread at a time.
• std::mutex has the member functions lock() and unlock() that lock and
unlock the mutex
• try_lock() is a member function that tries to lock the mutex and returns
true if it was successful
• All three functions may be called simultaneously by different threads
• For each call to lock() the same thread must call unlock() exactly once
std::mutex printMutex;
void safe_print(int i) {
printMutex.lock();
std::cout << i;
printMutex.unlock();
}
544
Multi-Threading Mutual Exclusion
Recursive Mutexes
Recursive mutexes are regular mutexes that additionally allow a thread that
currently holds the mutex to lock it again.
• Implemented in the class std::recursive_mutex
• Has the same member functions as std::mutex
• unlock() must still be called once for each lock()
• Useful for functions that call each other and use the same mutex
std::recursive_mutex m;
void foo() {
m.lock();
std::cout << "foo\n";
m.unlock();
}
void bar() {
m.lock();
std::cout << "bar\n";
foo(); // This will not deadlock
m.unlock();
}
545
Multi-Threading Mutual Exclusion
A shared mutex is a mutex that differentiates between shared and exclusive locks.
• Implemented in the class std::shared_mutex
• A shared mutex can either be locked exclusively by one thread or have
multiple shared locks
• The member functions lock() and unlock() are exclusive
• The member functions lock_shared() and unlock_shared() are shared
• The member functions try_lock() and try_lock_shared() try to get an
exclusive or shared lock and return true on success
546
Multi-Threading Mutual Exclusion
547
Multi-Threading Mutual Exclusion
Note the following when using mutexes to make data structures thread-safe:
• The member functions lock() and unlock() are non-const
• If const member functions of the data structure should also use the mutex, it
should be mutable
• If a member function that locks the mutex calls other member functions, this
can lead to deadlocks
• recursive_mutex can be used to avoid this
548
Multi-Threading Mutual Exclusion
std::mutex m;
int i = 0;
std::thread t{[&] {
std::unique_lock l{m}; // m.lock() is called
++i;
// m.unlock() is called
}};
549
Multi-Threading Mutual Exclusion
std::shared_mutex m;
int i = 0;
std::thread t{[&] {
std::shared_lock l{m}; // m.lock_shared() is called
std::cout << i;
// m.unlock_shared() is called
}};
550
Multi-Threading Mutual Exclusion
551
Multi-Threading Mutual Exclusion
Here, calling threadA() and threadB() concurrently will not lead to deadlocks.
Note: This should only be used if there is no other way as it is generally very
inefficient!
552
Multi-Threading Mutual Exclusion
553
Multi-Threading Mutual Exclusion
554
Multi-Threading Mutual Exclusion
One use case for condition variables are worker queues: Tasks are inserted into a
queue and then worker threads are notified to do the task.
555
Multi-Threading Atomic Operations
Atomic Operations
Threads can also be synchronized with atomic operations that are usually much
more efficient than mutexes.
• Atomicity means that an operation is executed as one unit, no other
operation on the same object can be executed at the same time
• Consider a function void atomic_add(int& p, int i) that represents an
atomic operation that adds i to the integer p and the following program:
int a = 10;
void threadA() { atomic_add(a, 1); }
void threadB() { atomic_add(a, 2); }
556
Multi-Threading Atomic Operations
• All classes and functions related to atomic operations can be found in the
<atomic> header
• std::atomic<T> is a class that represents an atomic version of the type T
• This class has several member functions that implement atomic operations:
• T load(): Loads the value (allows concurrent writes)
• void store(T desired): Stores desired in the object
• T exchange(T desired): Stores desired in the object and returns the old
value
• bool compare_exchange_weak(...) and
bool compare_exchange_strong(...): Performs a compare-and-swap
(CAS) operation
• If T is a integral type, the following operations also exist:
• T fetch_add(T arg): Adds arg to the value and returns the old value
• T fetch_sub(T arg): Same for subtraction
• T fetch_and(T arg): Same for bitwise and
• T fetch_or(T arg): Same for bitwise or
• T fetch_xor(T arg): Same for bitwise xor
557
Multi-Threading Atomic Operations
Modification Order
All modifications of a single atomic object are totally ordered in the so-called
modification order.
• The modification order is consistent between threads (i.e. all threads see the
same order)
• The modification order is only total for individual atomic objects
std::atomic<int> i = 0;
void workerThread() {
i.fetch_add(1); // (A)
i.fetch_sub(1); // (B)
}
void readerThread() {
int iLocal = i.load();
assert(iLocal == 0 || iLocal == 1); // This is always true
}
Because the memory order is consistent between threads, the reader thread will
never see a execution order of (A), (B), (B), (A), for example.
558
Multi-Threading Atomic Operations
The following values can be used as function arguments to the atomic operations
to specify the memory order:
• std::memory_order_relaxed: Relaxed memory order
• std::memory_order_seq_cst: Sequentially consistent memory order
std::atomic<int> i;
// Sequentially consistent memory order is the default
i.store(123);
// Relaxed atomic addition
i.fetch_add(321, std::memory_order_relaxed);
// Explicitly specify sequential consistency
i.store(1, std::memory_order_seq_cst);
560
Multi-Threading Atomic Operations
std::atomic<int> a = 0;
std::atomic<int> b = 0;
void threadA() {
int a_local = a.load(std::memory_order_relaxed); // A1
b.store(a_local, std::memory_order_relaxed); // A2
safe_print(a_local);
}
void threadB() {
int b_local = b.load(std::memory_order_relaxed); // B1
a.store(5, std::memory_order_relaxed); // B2
safe_print(b_local);
}
This program is allowed to print “55”! This is because the operations in lines B1
and B2 are allowed to be reordered by the CPU.
561
Multi-Threading Atomic Operations
The execution of the last example could look like this on an x86 CPU:
Assembly: Execution on the CPU:
threadA():
Thread A A1 A2
movl a(%rip), %edi (A1)
movl %edi, b(%rip) (A2) Thread B B1
jmp safe_print(int) B2
threadB():
movl b(%rip), %edi (B1) • A2 has a data dependency on A1, so it must
movl $5, a(%rip) (B2) wait for A1 to be finished
jmp safe_print(int) • B1 and B2 are independent
• In thread A, B2 happens before A1
• In thread B, A2 happens before B1
562
Multi-Threading Atomic Operations
563
Multi-Threading Atomic Operations
bool compare_and_swap(
int* atomic_variable, int& expected, int desired
) {
int value = *atomic_variable;
if (value == expected) {
*atomic_variable = desired;
return true;
} else {
expected = value;
return false;
}
}
564
Multi-Threading Atomic Operations
CAS operations are usually used in a loop with the following steps:
1. Load value of atomic variable into local variable
2. Do computation with the local variable assuming that no other thread will
modify the value of the atomic variable
3. Generate new desired value for the atomic variable
4. Do a CAS operation on the atomic variable with the local variable as
expected value
5. Start the loop from the beginning if the CAS operation fails
Note that steps 2 and 3 are allowed to use operations that are not atomic!
565
Multi-Threading Atomic Operations
566
Multi-Threading Atomic Operations
The std::atomic class has two member functions for CAS operations:
compare_exchange_weak() and compare_exchange_strong().
Both versions have three parameters:
• The expected value
• The desired value
• The memory order (optional, default is std::memory_order_seq_cst)
The weak version is allowed to return false, even when no other thread modified
the value. This is called “spurious failure”.
General rule: If you use a CAS operation in a loop, always use the weak version.
567
Organizing Larger Projects
568
Organizing Larger Projects
Overview
This lecture attempts to give some suggestions and an overview of useful tools
• Project layout suggestions (tailored to CMake)
• Integrating third-party tools and libraries with CMake
• Advanced debugging facilities
• We do not claim completeness or bias-free presentation
• Refer to the CMake documentation for much more detail
569
Organizing Larger Projects Project Layout
Changes to one of these properties likely entail changes to the other properties
• Namespace structure should (roughly) reflect directory structure and
vice-versa
• Different libraries and executables ideally reside in separate source trees (i.e.
directories)
570
Organizing Larger Projects Project Layout
General guidelines
• Always clearly organize files, directories and namespaces with modularization
in mind
• Start with a monolithic library/executable structure and move to a more
independent and modular structure as the project grows
571
Organizing Larger Projects Project Layout
Directory Structure
572
Organizing Larger Projects Project Layout
Evolution
• Eventually, some library or executable in a small project will grow large
• Should then be moved into an independent (sub-)project
573
Organizing Larger Projects Project Layout
574
Organizing Larger Projects Project Layout
Evolution
• Eventually other projects or people may want to reuse one of the subprojects
in a different context
• Should then be moved into an entirely independent project
575
Organizing Larger Projects Project Layout
576
Organizing Larger Projects Project Layout
File content
• Generally, there should be one separate pair of header and implementation
files for each C++ class
• Very tightly coupled classes (e.g. classes that could also be nested classes)
can be placed in the same header and implementation files
File location
• Option 1: Place associated implementation and header files in the same
directory (preferred by us)
• Option 2: Place associated implementation and header files in separate
directory trees (e.g. src and include)
• Option 1 makes browsing code somewhat easier, option 2 makes system-wide
installation easier
577
Organizing Larger Projects Project Layout
578
Organizing Larger Projects Project Layout
579
Organizing Larger Projects Project Layout
Include Directories
Usually, the include path for a library should contain a prefix
• E.g. includes for a library “foo” could start with #include "foo/..."
• Requires a suitable directory structure in the source tree of the library
• Usually requires the use of target_include_directories in the
CMakeLists.txt
580
Organizing Larger Projects Libraries & Executables
582
Organizing Larger Projects Libraries & Executables
set(MY_EXECUTABLE_SOURCES
src/my_executable/Helper.cpp
...
src/my_executable/Main.cpp
)
add_executable(my_executable ${MY_EXECUTABLE_SOURCES})
583
Organizing Larger Projects Libraries & Executables
Static Libraries
584
Organizing Larger Projects Libraries & Executables
Shared Libraries
585
Organizing Larger Projects Libraries & Executables
Advantages
• Can have slightly higher performance since there are no indirections
• No compatibility issues since there are no external dependencies
Disadvantages
• Much bigger file sizes than shared libraries since code is actually copied
• Programs depending on static libraries have to be recompiled if the static
library changes
586
Organizing Larger Projects Libraries & Executables
Advantages
• Much smaller file sizes since the shared library is only loaded into memory at
run time
• Much lower memory consumption since only a single copy of a shared library
is kept in memory (even for unrelated processes)
• Can be exchanged for other compatible versions without changing programs
that depend on a shared library
Disadvantages
• Programs depending on a shared library rely on a compatible version being
available
• Can be slightly slower due to additional indirection at runtime
587
Organizing Larger Projects Libraries & Executables
588
Organizing Larger Projects Libraries & Executables
set(MY_LIBRARY_SOURCES
src/my_library/ClassA.cpp
...
src/my_library/ClassZ.cpp
)
589
Organizing Larger Projects Libraries & Executables
590
Organizing Larger Projects Libraries & Executables
set(MY_LIBRARY_SOURCES
src/my_library/ClassA.cpp
...
src/my_library/ClassZ.cpp
)
591
Organizing Larger Projects Libraries & Executables
Usually only the implementation files (*.cpp) should be added to a CMake target
• Header files on their own are not compiled
• Only headers that are included by implementation files are relevant for
compilation
592
Organizing Larger Projects Libraries & Executables
add_library(my_library INTERFACE)
target_include_directories(my_library INTERFACE src)
target_link_libraries(my_library INTERFACE some_dependency)
593
Organizing Larger Projects Libraries & Executables
594
Organizing Larger Projects Libraries & Executables
cmake_minimum_required(VERSION 3.12)
project(project)
add_subdirectory(my_executable)
add_subdirectory(my_library)
595
Organizing Larger Projects Libraries & Executables
596
Organizing Larger Projects Libraries & Executables
597
Organizing Larger Projects Libraries & Executables
set(MY_EXECUTABLE_SOURCES
src/my_executable/Helper.cpp
...
src/my_executable/Main.cpp
)
add_executable(my_executable ${MY_EXECUTABLE_SOURCES})
# allows includes to be '#include "my_executable/..."
# instead of '#include "my_executable/src/my_executable/..."
target_include_directories(my_executable PRIVATE src/)
# dependency on the my_libary target defined in other subproject
target_link_libraries(my_executable PRIVATE my_library)
598
Organizing Larger Projects Libraries & Executables
Paths in CMake
CMake defines several variables for often-used paths
CMAKE_SOURCE_DIR
Contains the full path to the top level of the source tree, i.e. the location of the
top-level CMakeLists.txt
CMAKE_CURRENT_SOURCE_DIR
Contains the full path the the source directory that is currently being processed
by CMake. Differs from CMAKE_SOURCE_DIR in directories added through
add_subdirectory.
CMAKE_BINARY_DIR
Contains the full path to the top level of the build tree, i.e. the build directory in
which cmake is invoked.
CMAKE_CURRENT_BINARY_DIR
Contains the full path the binary directory that is currently being processed.
Each directory added through add_subdirectory will create a corresponding
binary directory in the build tree.
Relative paths are usually relative to the current source directory
599
Organizing Larger Projects Third-Party Libraries
Third-Party Libraries
If possible and feasible, your project should not bundle third-party dependencies
• Many libraries can easily be installed through a package manager
• Reduces complexity of project configuration and maintenance
• CMake provides facilities for locating third-party dependencies in a
platform-independent way
600
Organizing Larger Projects Third-Party Libraries
find_package (1)
601
Organizing Larger Projects Third-Party Libraries
find_package (2)
Example
...
add_executable(tester ...)
target_link_libraries(tester PRIVATE
...
GTest::GTest # Imported target for the gtest library
# as specified by the documentation of
# FindGTest
)
602
Organizing Larger Projects Third-Party Libraries
find_library (1)
603
Organizing Larger Projects Third-Party Libraries
find_library (2)
Example (assuming there is no FindGTest.cmake script)
...
add_executable(tester ...)
target_link_libraries(tester PRIVATE
...
GTest # Only adds the libgtest library
# Does not set include paths
)
604
Organizing Larger Projects Third-Party Libraries
Further Reading
605
Organizing Larger Projects Testing
Testing
606
Organizing Larger Projects Testing
Googletest (1)
Functionality overview
• Test cases
• Predefined and user-defined assertions
• Death tests
• …
607
Organizing Larger Projects Testing
Googletest (2)
Simple tests
#include <gtest/gtest.h>
//--------------------------------------------------------
TEST(TestSuiteName, TestName) {
...
}
608
Organizing Larger Projects Testing
Googletest (3)
Fatal assertions
• Fatal assertions are prefixed with ASSERT_
• When a fatal assertion fails the test function is immediately terminated
Non-fatal assertions
• Non-fatal assertions are prefixed with EXPECT_
• When a non-fatal assertion fails the test function is allowed to continue
• Nevertheless the test case will fail
• All assertions exist in fatal and non-fatal versions
Assertion examples
• ASSERT_TRUE(condition); or ASSERT_FALSE(condition);
• ASSERT_EQ(val1, val2); or ASSERT_NE(val1, val2);
• …
609
Organizing Larger Projects Testing
Googletest (4)
#include <gtest/gtest.h>
//--------------------------------------------------------
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
610
Organizing Larger Projects Testing
Coverage (1)
611
Organizing Larger Projects Testing
Coverage (2)
Brief example
612
Organizing Larger Projects Further Tools & Techniques
Continuous Integration
613
Organizing Larger Projects Further Tools & Techniques
Code Formatting
614
Organizing Larger Projects Further Tools & Techniques
Linting
615
Organizing Larger Projects Further Tools & Techniques
perf (1)
616
Organizing Larger Projects Further Tools & Techniques
perf (2)
perf stat example
> perf stat --detailed ./my_executable
...
Performance counter stats for './my_executable':
617
Organizing Larger Projects Further Tools & Techniques
Valgrind
Use cases
• Complex memory bugs that are not detected by simpler tools like the address
sanitizer
• Complex profiling tasks
618
Organizing Larger Projects Further Tools & Techniques
Regular debuggers like GDB can only step forward in the program
• Does not necessarily fit debugging requirements
• E.g. when a crash occurs, we would like to step backwards until we have
found the source of the crash
619
Organizing Larger Projects Further Tools & Techniques
void bar() {
assert((a % 2) == 0);
a = (a + 2) % max;
}
};
//--------------------------------------------------------
int main() {
Foo foo;
for (unsigned i = 0; i < 16; ++i)
foo.bar();
}
620
Organizing Larger Projects Further Tools & Techniques
Old value = 1
New value = 14
0x00005568dba67208 in Foo::bar (this=0x7fff75f38980) at main.cpp:9
9 a = (a + 2) % max;
621
C++ Systems Programming on Linux
622
C++ Systems Programming on Linux
Until now, most topics were about standard C++. The standard does not contain
everything that is useful for good systems programming, such as:
• Creating, removing, renaming files and directories
• Efficient reading and writing of files
• Direct manual memory allocation from the kernel
• Networking
• Management of processes and threads
The Linux kernel in particular has a very extensive user-space C-API that can be
used to directly communicate with the kernel for all of those tasks.
623
C++ Systems Programming on Linux
624
C++ Systems Programming on Linux Interacting with Files
File Descriptors
A very central concept in the POSIX API are so called file descriptors (fds).
• File descriptors have the type int
• They are used as a “handle” to:
• Files in the filesystem
• Directories in the filesystem
• Network sockets
• Many other kernel objects
• Usually, fds are created by a function (e.g. open()) and must be closed by
another function (e.g. close())
• When working with fds in C++, the RAII pattern can be very useful
625
C++ Systems Programming on Linux Interacting with Files
To open and create files the open() function can be used. It must be included
from <sys/stat.h> and <fcntl.h>.
• int open(const char* path, int flags, mode_t mode)
• Opens the file at path with the given flags and returns an fd for that file
• If an error occurs, -1 is returned
• The third argument mode is optional and only required when a file is created
• flags is a bitmap (created with bitwise or) that must contain exactly one of
the following flags:
O_RDONLY Open the file only for reading.
O_RDWR Open the file for reading and writing.
O_WRONLY Open the file only for writing.
• close() must be used to close the fd returned by open()
626
C++ Systems Programming on Linux Interacting with Files
There are more flags that can combined with bitwise or:
O_CREAT If the file does not exist, it is created with the permission
bits taken from the mode argument
O_EXCL Can only be used in combination with O_CREAT. Causes
open() to fail and return an error when the file exists.
O_TRUNC If the file exists and it is opened for writing, truncate the
file, i.e. remove all its contents and set its length to 0.
Example:
#include <fcntl.h>
#include <sys/stat.h>
int main() {
int fd = open("/tmp/testfile", O_WRONLY | O_CREAT, 0600);
if (fd < 0) { /* error */ }
else { close(fd); }
}
627
C++ Systems Programming on Linux Interacting with Files
To read from and write to files, read() and write() from the header
<unistd.h> can be used.
• ssize_t read(int fd, void* buf, size_t count)
• ssize_t write(int fd, const void* buf, size_t count)
• fd must be a valid file descriptor
• buf must be a memory buffer which has a size of at least count bytes
• The return value indicates how many bytes were actually read or written (can
be up to count)
• Both functions return -1 when an error occurs
• Note: Both functions may wait until data can actually be read or written
which can lead to deadlocks!
628
C++ Systems Programming on Linux Interacting with Files
For an opened file the kernel remembers the current position in the file.
• read() and write() start reading or writing from the current position
• They both advance the current position by the number of bytes read or
written
The function lseek() (headers <sys/types.h> and <unistd.h>) can be used
to get or set the current position.
• off_t lseek(int fd, off_t offset, int whence)
• off_t is a signed integer type
• The current position is changed according to offset and whence, which is
one of the following:
SEEK_SET The current position is set to offset
SEEK_CUR offset is added to the current position
SEEK_END The current position is set to the end of the file plus offset
• lseek() returns the value of the new position, or -1 if an error occurred
629
C++ Systems Programming on Linux Interacting with Files
Example:
Note: The current position is shared between all threads. Generally, read(),
write(), and lseek() should not be used concurrently on the same fd.
630
C++ Systems Programming on Linux Interacting with Files
There also exist two functions that read or write from a file without using the
current position: pread() and pwrite() from the header <unistd.h>.
• ssize_t pread(int fd, void* buf, size_t count, off_t offset)
• ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset)
• Conceptually, those functions work like lseek(fd, offset, SEEK_SET)
followed by read() or write()
• However, they do not modify the current position in the file
• Should be used when reading from and writing to files from multiple threads
631
C++ Systems Programming on Linux Interacting with Files
Meta data of files, such as the type of a file, its size, its owner, or the date it was
last modified, can be read with stat() or fstat(). Required headers:
<sys/types.h>, <sys/stat.h>, <unistd.h>.
• int stat(const char* filename, struct stat* statbuf)
• int fstat(int fd, struct stat* statbuf)
• The meta data of the file specified by filename or fd is written into
statbuf
• Returns 0 on success, -1 on error
• struct stat has several member variables:
mode_t st_mode The file mode (S_IFREG for regular file, S_IFDIR for
directory, S_IFLNK for symbolic link, …)
uid_t st_uid The user id of the owner
off_t st_size The total size in bytes
…
632
C++ Systems Programming on Linux Interacting with Files
633
C++ Systems Programming on Linux Interacting with Files
POSIX and Linux have many more functions that deal with files and directories:
mkdir() Create a directory
mkdirat() Create a subdirectory in a specific directory
openat() Open a file in a specific directory
unlink() Remove a file
unlinkat() Remove a file from a specific directory
rmdir() Remove an empty directory
chmod()/fchmod() Change the permissions of a file
chown()/fchown() Change the owner of a file
fsync() Force changes to a file to be written
…
634
C++ Systems Programming on Linux Memory Mapping
Memory Mapping
POSIX defines the function mmap() in the header <sys/mman.h> which can be
used to manage the virtual address space of a process.
• void* mmap(void* addr, size_t length, int prot, int flags,
int fd, off_t offset)
• Arguments have different meaning depending on flags
• On error, the special value MAP_FAILED is returned
• Always: If a pointer is returned successfully, it must be freed with munmap()
• int munmap(void* addr, size_t length)
• addr must be a value returned from mmap()
• length must be the same value passed to mmap()
• RAII should be used to ensure that munmap() is called
635
C++ Systems Programming on Linux Memory Mapping
• Note: This assumes that integers are written in binary format to the file!
• Using mmap() to read from large files is often faster than using read()
• This is because with mmap() data is directly read from and written to the file
without copying it to a buffer first
637
C++ Systems Programming on Linux Memory Mapping
638
C++ Systems Programming on Linux Process Management
One possible output for this example is: start old end new end
639
C++ Systems Programming on Linux Process Management
Thread Pinning
Threads can control on which physical CPU cores they run by using
sched_setaffinity() from <sched.h>.
• int sched_setaffinitiy(pid_t pid, size_t cpusetsize
const cpu_set_t* mask)
• pid stands for the process id whose affinity should be set, or 0 which stands
for the current thread
• cpusetsize must be set to sizeof(cpu_set_t)
• mask is a pointer to a cpu_set_t which describes which CPU cores the
thread is allowed to run on
• Returns 0 on success, -1 on error
• Variables of type cpu_set_t can be modified with
CPU_ZERO(cpu_set_t* set) and CPU_SET(int cpu, cpu_set_t* set)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set); CPU_SET(4, &set);
sched_setaffinity(0, sizeof(cpu_set_t), &set);
642
C++ Systems Programming on Linux Process Management
Signals
643
C++ Systems Programming on Linux Process Management
Signal handlers can set by using sigaction() from the header <signal.h>.
• int sigaction(int signum, const struct sigaction* act,
struct sigaction* sigact)
• signum is the signal whose signal handler should be changed
• act is a pointer to the signal handler that should be set, or nullptr if an
existing signal handler should be removed
• If sigact is not nullptr, it will contain the old signal handler after the
function returns
• Returns 0 on success, -1 on error
• struct sigaction has several members, the most important one is:
void (*sa_handler)(int)
• sa_handler is a function pointer that points to the signal handler function
that takes the signal as only argument
644
C++ Systems Programming on Linux Process Management
As signal handlers can be called at any time while other code is running, they
should avoid to interfere with memory that is currently accessed.
645
C++ Systems Programming on Linux Process Management
Sending Signals
A process can send a signal to itself or other process by using kill() from the
headers <sys/types.h> and <signal.h>.
• int kill(pid_t pid, int sig)
• pid is the process id of the process that should recieve the signal
• If pid is 0, the signal is sent to all processes in the process group (i.e. to all
other threads)
• If pid is -1, the signal is sent to all processes for which the calling process
has the permission
• Returns 0 on success, -1 on error
• With the signals SIGUSR1 and SIGUSR2 (“user-defined signals”) this can be
used for (limited) communication between processes
646
C++ Systems Programming on Linux Process Management
int fds[2];
pipe(fds);
int readfd = fds[0]; int writefd = fds[1];
write(writefd, "hello", 5);
char buffer[5];
read(readfd, buffer, 5); // buffer now contains "hello"
close(readfd); close(writefd);
647
C++ Systems Programming on Linux Process Management
648
C++ Systems Programming on Linux Error Handling
Error Handling
Most functions use errno from the header <cerrno> for error handling.
• errno is a global variable that contains an error code
• Is set when a function returns an error (e.g. by returning -1)
• All possible values for errno are available as constants:
EINVAL Invalid argument
ENOENT No such file or directory (e.g. in open())
EACCES Permission denied
ENOMEM Not enough memory (e.g. for mmap())
…
• A description of the error can be retrieved with std::strerror() from
<cstring>
649
Miscellaneous
Miscellaneous
650
Miscellaneous Tricks on x86-64
The upper 16 bit of each pointer can be used to store useful information
• Usually called pointer tagging
• Tagged pointers require careful treatment to avoid memory bugs
• If portability is desired, an implementation that works without pointer
tagging has to be provided (e.g. through preprocessor defines)
• Allows us to modify two values (16 bit tag and 48 bit pointer) with a single
atomic instruction
651
Miscellaneous Tricks on x86-64
Guidelines
• Always wrap tagged pointers within a suitable data structure
• Do not expose tagged pointers in raw form
• Store tagged pointers as uintptr_t internally
• Use bit operations to access tag and pointer parts
652
Miscellaneous Tricks on x86-64
653
Miscellaneous Tricks on x86-64
654
Miscellaneous Vectorization
Vectorization
Most modern CPUs contain vector units that can exploit data-level parallelism
• Apply the same operation (e.g. addition) to multiple data elements in a single
instruction
• Can greatly improve the performance of suitable algorithms (e.g. image
processing)
• Not all algorithms are amenable to vectorization
Overview
• Can be used through extensions to the x86 instruction set architecture
• Commonly referred to as single instruction, multiple data (SIMD) instructions
• Can be used in C/C++ code through intrinsic functions
• The Intel Intrinsics Guide provides an excellent documentation
655
Miscellaneous Vectorization
SIMD Extensions
Modern CPUs retain backward compatibility with older instruction set extensions
• The CPU flags exposed in /proc/cpuinfo indicate which extensions are
supported
• We will briefly introduce AVX (avx flag in /proc/cpuinfo)
• AVX should be supported on most reasonably modern CPUs
656
Miscellaneous Vectorization
AVX data types and intrinsics are defined in the <immintrin.h> header
• AVX adds 16 registers which are 256 bits wide each
• Can hold multiple data elements
• Can be used through special opaque data types
Other SIMD extensions follow similar naming conventions for data types
657
Miscellaneous Vectorization
AVX Intrinsics
658
Miscellaneous Vectorization
Constant Values
659
Miscellaneous Vectorization
660
Miscellaneous Vectorization
Arithmetic Operations
661
Miscellaneous Vectorization
Example
Computing the sum of elements in an std::vector
#include <immintrin.h>
#include <vector>
//--------------------------------------------------------------------
float fastSum(const std::vector<float>& vec) {
__m256 vectorSum = _mm256_set1_ps(0);
uint64_t index;
for (index = 0; (index + 8) <= vec.size(); index += 8) {
__m256 data = _mm256_loadu_ps(&vec[index]);
vectorSum = _mm256_add_ps(vectorSum, data);
}
float sum = 0;
float buffer[8];
_mm256_storeu_ps(buffer, vectorSum);
for (unsigned i = 0; i < 8; ++i)
sum += buffer[i];
for (; index < vec.size(); ++index)
sum += vec[index];
return sum;
}
662
Miscellaneous Vectorization
Further Operations
663
Miscellaneous Outlook on C++20
664
Miscellaneous Outlook on C++20
665
Miscellaneous Outlook on C++20
666
Miscellaneous Outlook on C++20
667
Miscellaneous Outlook on C++20
668
Miscellaneous Outlook on C++20
669
Miscellaneous Outlook on C++20
670
Miscellaneous Outlook on C++20
671
Miscellaneous Outlook on C++20
The compiler will now check that all constraints are fulfilled
672
Miscellaneous Outlook on C++20
673
Miscellaneous Outlook on C++20
Contracts (1)
Outline
• Preconditions and postconditions are function attributes
• Are evaluated immediately before evaluating the function body / immediately
before returning
• Assertions can appear within a function body
• Programs can be compiled with different build levels that affect contract
checking
674
Miscellaneous Outlook on C++20
Contracts (2)
Example (C++17)
// postcondition
assert(result < max);
return result;
}
675
Miscellaneous Outlook on C++20
Contracts (3)
Example (C++20)
676
Miscellaneous Outlook on C++20
677
Miscellaneous Outlook on C++20
std::span (1)
Benefits
• Similar benefits as std::string_view
• Lightweight proxy for a range of objects
• Constant-time operations
678
Miscellaneous Outlook on C++20
std::span (2)
Example
// C++17
void foo17(unsigned* begin, unsigned* end) {
// do something
// do something more
}
//---------------------------------------------------
// C++20
void foo20(std::span<unsigned> span) {
// do something
// do something more
}
679
Miscellaneous Outlook on C++20
Ranges
#include <iostream>
#include <ranges>
#include <vector>
//---------------------------------------------------
int main() {
std::vector<unsigned> v{0, 1, 2, 3, 4, 5, 6};
auto even = [](unsigned i) { return (i % 2 == 0); }
auto square = [](unsigned i) { return i * i; }
680
Miscellaneous Outlook on C++20
Modules (1)
681
Miscellaneous Outlook on C++20
Modules (2)
Example
greeting.cpp
export module greeting;
import <string>;
main.cpp
import greeting;
import <iostream>;
int main() {
std::cout << getGreeting() << std::endl;
}
682
Miscellaneous Outlook on C++20
Coroutines
generator<int> iota(int n = 0) {
while (true)
co_yield n++;
}
683