0% found this document useful (0 votes)
2K views284 pages

CPP Interview

Uploaded by

nadav.hugim
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2K views284 pages

CPP Interview

Uploaded by

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

Daily C++ Interview

Prepare yourself for your next


interview one question a day

Sandor Dargo
This book is for sale at http://leanpub.com/cppinterview

This version was published on 2022-09-05

This is a Leanpub book. Leanpub empowers authors and


publishers with the Lean Publishing process. Lean Publishing is
the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have
the right book and build traction once you do.

© 2021 - 2022 Sandor Dargo


Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

The different typical interview processes . . . . . . . . . . 2


The Big Tech style interview process . . . . . . . . . . . . 2
The shorter interview process . . . . . . . . . . . . . . . . 4

auto and type deduction . . . . . . . . . . . . . . . . . . . . 5


Question 1: Explain auto type deduction! . . . . . . . . . 5
Question 2: When can auto deduce undesired types? . . 7
Question 3: What are the advantages of using auto? . . . 8
Question 4: What is the type of myCollection after the
following declaration? . . . . . . . . . . . . . . . . 9
Question 5: What are trailing return types? . . . . . . . . 11
Question 6: Explain decltype! . . . . . . . . . . . . . . . . 12
Question 7: When to use decltype(auto)? . . . . . . . . 14
Question 8: Which data type do you get when you add
two bools? . . . . . . . . . . . . . . . . . . . . . . . 16

The keyword static and its different usages . . . . . . . . 18


Question 9: What does a static member variable in C++
mean? . . . . . . . . . . . . . . . . . . . . . . . . . 18
Question 10: What does a static member function mean
in C++? . . . . . . . . . . . . . . . . . . . . . . . . 19
Question 11: What is the static initialization order fiasco? 21
Question 12: How to solve the static initialization order
fiasco? . . . . . . . . . . . . . . . . . . . . . . . . . 23
CONTENTS

Polymorphism, inheritance and virtual functions . . . . . 26


Question 13: What is the difference between function
overloading and function overriding? . . . . . . . 26
Question 14: What is a virtual function? . . . . . . . . . 28
Question 15: What is the override keyword and what
are its advantages? . . . . . . . . . . . . . . . . . . 29
Question 16: Explain the concept of covariant return
types and show a use-case where it comes in handy! 32
Question 17: What is virtual inheritance in C++ and
when should you use it? . . . . . . . . . . . . . . . 34
Question 18: Should we always use virtual inheritance?
If yes, why? If not, why not? . . . . . . . . . . . . 38
Question 19: What’s the output of the following sample
program? Is that what you’d expect? Why? Why
not? . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Question 20: Can you access the public and protected
members and functions of a base class if you have
private inheritance? . . . . . . . . . . . . . . . . . 44
Question 21: What is private inheritance used for? . . . . 46
Question 22: Can you call a virtual function from a
constructor or a destructor? . . . . . . . . . . . . . 49
Question 23: What role does a virtual destructor play? . 51
Question 24: Can we inherit from a standard container
(such as std::vector)? If so what are the implica-
tions? . . . . . . . . . . . . . . . . . . . . . . . . . 52
Question 25: What does a strong type mean and what
advantages does it have? . . . . . . . . . . . . . . 55
Question 26: Explain short-circuit evaluation . . . . . . . 57
Question 27: What is a destructor and how can we
overload it? . . . . . . . . . . . . . . . . . . . . . . 59
Question 28: What is the output of the following piece of
code and why? . . . . . . . . . . . . . . . . . . . . 60
Question 29: How to use the = delete specifier in C++? 61

Lambda functions . . . . . . . . . . . . . . . . . . . . . . . . 65
CONTENTS

Question 30: What are immediately invoked lambda


functions? . . . . . . . . . . . . . . . . . . . . . . . 65
Question 31: What kind of captures are available for
lambda expressions? . . . . . . . . . . . . . . . . . 67

How to use the const qualifier in C++ . . . . . . . . . . . . 71


Question 32: What is the output of the following piece of
code and why? . . . . . . . . . . . . . . . . . . . . 71
Question 33: What are the advantages of using const
local variables? . . . . . . . . . . . . . . . . . . . . 72
Question 34: Is it a good idea to have const members in
a class? . . . . . . . . . . . . . . . . . . . . . . . . . 73
Question 35: Does it make sense to return const objects
by value? . . . . . . . . . . . . . . . . . . . . . . . 77
Question 36: How should you return const pointers from
a function? . . . . . . . . . . . . . . . . . . . . . . 79
Question 37: Should functions return const references? . 81
Question 38: Should you take plain old data types by
const reference as a function parameter? . . . . . 83
Question 39: Should you pass objects by const reference
as a function parameter? . . . . . . . . . . . . . . 85
Question 40: Does the signature of a function declaration
has to match the signature of the function defini-
tion? . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Question 41: Explain what consteval and constinit
bring to C++? . . . . . . . . . . . . . . . . . . . . . 89

Some best practices in modern C++ . . . . . . . . . . . . . . 91


Question 42: What is aggregate initialization? . . . . . . 91
Question 43: What are explicit constructors and what are
their advantages? . . . . . . . . . . . . . . . . . . . 94
Question 44: What are user-defined literals? . . . . . . . 97
Question 45: Why should we use nullptr instead of NULL
or 0? . . . . . . . . . . . . . . . . . . . . . . . . . . 98
CONTENTS

Question 46: What advantages does alias have over


typedef? . . . . . . . . . . . . . . . . . . . . . . . . 100
Question 47: What are the advantages of scoped enums
over unscoped enums? . . . . . . . . . . . . . . . . 101
Question 48: Should you explicitly delete unused/unsup-
ported special functions or declare them as private? 103
Question 49: How to use the = delete specifier in C++? . 105
Question 50: What is a trivial class in C++? . . . . . . . . 108

Smart pointers . . . . . . . . . . . . . . . . . . . . . . . . . . 111


Question 51: Explain the Resource acquisition is initial-
ization (RAII) idiom . . . . . . . . . . . . . . . . . 111
Question 52: When should we use unique pointers? . . . 114
Question 53: What are the reasons to use shared pointers? 116
Question 54: When to use a weak pointer? . . . . . . . . 117
Question 55: What are the advantages of std::make_-
shared and std::make_unique compared to the
new operator? . . . . . . . . . . . . . . . . . . . . . 119
Question 56: Should you use smart pointers over raw
pointers all the time? . . . . . . . . . . . . . . . . . 120
Question 57: When and why should we initialize point-
ers to nullptr? . . . . . . . . . . . . . . . . . . . . . 121

References, universal references, a bit of a mixture . . . . 124


Question 58: What does std::move move? . . . . . . . . . 124
Question 59: What does std::forward forward? . . . . . 125
Question 60: What is the difference between universal
and rvalue references? . . . . . . . . . . . . . . . . 126
Question 61: What is reference collapsing? . . . . . . . . 127
Question 62: When constexpr functions are evaluated? . 129
Question 63: When should you declare your functions as
noexcept? . . . . . . . . . . . . . . . . . . . . . . . 130

C++20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Question 64: What are concepts in C++? . . . . . . . . . 132
CONTENTS

Question 65: What are the available standard attributes


in C++? . . . . . . . . . . . . . . . . . . . . . . . . 134
Question 66: What is 3-way comparison? . . . . . . . . . 136
Question 67: Explain what consteval and constinit bring
to C++? . . . . . . . . . . . . . . . . . . . . . . . . 136
Question 68: What are modules and what advantages do
they bring? . . . . . . . . . . . . . . . . . . . . . . 137

Special function and the rules of how many? . . . . . . . . 140


Question 69: Explain the rule of three . . . . . . . . . . . 140
Question 70: Explain the rule of five . . . . . . . . . . . . 141
Question 71: Explain the rule of zero . . . . . . . . . . . . 142
Question 72: What does std::move move? . . . . . . . . . 144
Question 73: What is a destructor and how can we
overload it? . . . . . . . . . . . . . . . . . . . . . . 145
Question 74: Should you explicitly delete unused/unsup-
ported special functions or declare them as private? 146
Question 75: What is a trivial class in C++? . . . . . . . . 148
Question 76: What advantages does having a default
constructor have? . . . . . . . . . . . . . . . . . . . 150

Object oriented design, inheritance, polymorphism . . . . 152


Question 77: What are the differences between a class
and a struct? . . . . . . . . . . . . . . . . . . . . . 152
Question 78: What is constructor delegation? . . . . . . . 154
Question 79: Explain the concept of covariant return
types and show a use-case where it comes in handy! 154
Question 80: What is the difference between function
overloading and function overriding? . . . . . . . 157
Question 81: What is the override keyword and what
are its advantages? . . . . . . . . . . . . . . . . . . 158
Question 82: Explain what is a friend class or a friend
function . . . . . . . . . . . . . . . . . . . . . . . . 161
Question 83: What are default arguments? How are they
evaluated in a C++ function? . . . . . . . . . . . . 163
CONTENTS

Question 84: What is this pointer and can we delete it? 165
Question 85: What is virtual inheritance in C++ and
when should you use it? . . . . . . . . . . . . . . . 168
Question 86: Should we always use virtual inheritance?
If yes, why? If not, why not? . . . . . . . . . . . . 172
Question 87: What does a strong type mean and what
advantages does it have? . . . . . . . . . . . . . . 173
Question 88: What are user-defined literals? . . . . . . . 175
Question 89: Why shouldn’t we use boolean arguments? 177
Question 90: Distinguish between shallow copy and deep
copy . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Question 91: Are class functions taken into consideration
as part of the object size? . . . . . . . . . . . . . . 180
Question 92: What does dynamic dispatch mean? . . . . 180
Question 93: What are vtable and vpointer? . . . . . . . 182
Question 94: Should base class destructors be virtual? . . 183
Question 95: What is an abstract class in C++? . . . . . . 185
Question 96: Is it possible to have polymorphic
behaviour without the cost of virtual functions? . 187
Question 97: How would you add functionality to your
classes with the Curiously Recurring Template
Pattern (CRTP)? . . . . . . . . . . . . . . . . . . . 189
Question 98: What are the good reasons to use init()
functions to initialize an object? . . . . . . . . . . 191

Observable behaviours . . . . . . . . . . . . . . . . . . . . . 193


Question 99: What is observable behaviour of code? . . . 193
Question 100: What are the characteristics of an ill-
formed C++ program? . . . . . . . . . . . . . . . . 194
Question 101: What is unspecified behaviour? . . . . . . 195
Question 102: What is implementation-defined behaviour? 198
Question 103: What is undefined behaviour in C++? . . . 199
Question 104: What are the reasons behind undefined
behaviour’s existence? . . . . . . . . . . . . . . . . 200
CONTENTS

Question 105: What approaches to take to avoid unde-


fined behaviour? . . . . . . . . . . . . . . . . . . . 201
Question 106: What is iterator invalidation? Give a few
examples. . . . . . . . . . . . . . . . . . . . . . . . 203

The Standard Template Library . . . . . . . . . . . . . . . . 205


Question 107: What is the STL? . . . . . . . . . . . . . . . 205
Question 108: What are the advantages of algorithms
over raw loops? . . . . . . . . . . . . . . . . . . . . 206
Question 109: Do algorithms validate ranges? . . . . . . 208
Question 110: Can you combine containers of different
sizes? . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Question 111: How is a vector’s memory layout organized? 211
Question 112: Can we inherit from a standard container
(such as std::vector)? If so what are the implications? 212
Question 113: What is the type of myCollection after the
following declaration? . . . . . . . . . . . . . . . . 215
Question 114: What are the advantages of const_iterators
over iterators? . . . . . . . . . . . . . . . . . . . . . 216
Question 115: Binary search an element with algorithms! 216
Question 116: What is an Iterator class? . . . . . . . . . . 218

Miscalleanous . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Question 117: Can you call a virtual function from a
constructor or a destructor? . . . . . . . . . . . . . 220
Question 118: What are default arguments? How are
they evaluated in a C++ function? . . . . . . . . . 222
Question 119: Can virtual functions have default argu-
ments? . . . . . . . . . . . . . . . . . . . . . . . . . 224
Question 120: Should base class destructors be virtual? . 228
Question 121: What is the function of the keyword
mutable? . . . . . . . . . . . . . . . . . . . . . . . . 230
Question 122: What is the function of the keyword volatile? 233
Question 123: What is an inline function? . . . . . . . . . 234
Question 124: What do we catch? . . . . . . . . . . . . . 235
CONTENTS

Question 125: What are the differences between refer-


ences and pointers? . . . . . . . . . . . . . . . . . 238
Question 126: Which of the following variable declara-
tions compile and what would be the value of a? 239
Question 127: What will the line of code below print out
and why? . . . . . . . . . . . . . . . . . . . . . . . 241
Question 128: Explain the difference between pre- and
post-increment/decrement operators . . . . . . . . 243
Question 129: What are the final values of a, b and c? . . 244
Question 130: Does this string declaration compile? . . . 245
Question 131: What are Default Member Initializers in
C++? . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Question 132: What is the most vexing parse? . . . . . . 249
Question 133: Does this code compile? If yes, what does
it do? If not, why not? . . . . . . . . . . . . . . . . 251
Question 134: What is std::string_view and why
should we use it? . . . . . . . . . . . . . . . . . . . 253
Question 135: How to check if a string starts or ends with
a certain substring? . . . . . . . . . . . . . . . . . 254
Question 136: What is RVO? . . . . . . . . . . . . . . . . 257
Question 137: How can we ensure the compiler performs
RVO? . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Question 138: What are the primary and mixed value
categories in C++? . . . . . . . . . . . . . . . . . . 261
Question 139: Can you safely compare signed and un-
signed integers? . . . . . . . . . . . . . . . . . . . . 264
Question 140: What is the return value of main and what
are the available signatures? . . . . . . . . . . . . 265
Question 141: Should you prefer default arguments or
overloading? . . . . . . . . . . . . . . . . . . . . . 266
Question 142: How many variables should you declare
on a line? . . . . . . . . . . . . . . . . . . . . . . . 267
Question 143: Should you prefer a switch statement or
chained if statements? . . . . . . . . . . . . . . . . 268
Question 144: What are include guards? . . . . . . . . . . 270
CONTENTS

Question 145: Should you use angle brackets(<filename>)


or double quotes(“filename”) to include? . . . . . 271
Question 146: How many return statements should you
have in a function? . . . . . . . . . . . . . . . . . . 272
Introduction
Daily C++ Interview is an around 150 day program and it offers you
questions and coding problems organized around different topics,
such as polymorphism, smart pointers, modern C++ best practices,
etc. Some questions come back multiple times during the 5 months
so that you better remember, yet there are more than 120 unique
questions.
I recommend you take a question every day, try to answer it on your
own and then read the answer. The depth of the answers is limited
due to space and time reasons, but for almost every question you
get a list of references, so you’re more than welcome to continue
the quest.
Before starting to take the questions, let’s discuss the different kinds
of interviews and see how Daily C++ Interview will help you nail
them.
The different typical
interview processes
In order to better understand where Daily C++ Interview can help
you prepare for your next job interview, let’s differentiate between
two typical interview processes.

The Big Tech style interview process


One of them, the more usual one nowadays is very competitive
and has several rounds. It starts with an initial screening call that
is often conducted by a non-technical person, a recruiter. However,
I’ve heard and seen cases where even the first contacts were made
by engineers so that they can see earlier whether you’d be a good
fit for the team.
This first round might have been preceded by a 0th round with
some takeaway exercise. The idea behind is that if you cannot prove
a certain level of expertise, they don’t want to waste their time on
the applicant. Sometimes this exercise is way too long and many
of us would outright reject such homework. If it’s more reasonable,
you can complete it in an hour or two.
After the screening, there is a technical round that is usually quite
broad and there is not a lot of time to go deep on the different topics.
It’s almost sure that you’ll get an easy or medium-level Leetcode-
style coding exercise. For those, you must be able to reason about
algorithmic complexities and it’s also very useful if you are more
than familiar with the standard library of your language. Apart
from a coding exercise, expect more general questions about your
chosen language.
The different typical interview processes 3

If you’re reading this book, most probably that’s C++ for you. By
understanding your language deeper - something this book helps
with - you’ll have a better chance to reach the next and usually
final round of interviews, the so-called on-site. Even if it’s online,
it might still be called on-site and it’s a series of interviews you
have to complete in one day or sometimes spanned over one day.
It typically has 3 or 4 different types of interviews.

• Behavioural interviews focusing on your soft skills


• A system design interview where you get a quite vague task
to design a system. You have to clarify what the requirements
are and you have to come up with the high-level architecture
and dig deeper into certain parts
• There are different kinds of coding interviews
- Coding exercises - You won’t be able to solve coding ex-
ercises with what you learn in this book, but you’ll be able
to avoid some pitfalls with a deeper understanding of the
language. Daily C++ Interview helps you to achieve that
understanding. In addition, you must practice on pages like
Leetcode, Hackerrank, Codingame, etc
- Debug interview. You receive a piece of code and you
have to find the bugs. Sometimes this can be called a code
review interview. It’s still about finding bugs. Personally, I
find it a bit deeper than a simple coding exercise. In a coding
interview, you are supposed to talk about design flaws, code
smells, and testability. If you know C++ well enough, if you
try to answer some of the questions of Daily C++ Interview
on a day-to-day basis, you’ll have a much better chance to
recognize bugs, smells, flaws and pass the interview.

Given the several rounds, scheduling conflicts and sometimes


flying in for the last round, such a process can go quite long, it
can easily take at least a month if not two.
The different typical interview processes 4

The shorter interview process


Certain companies try to compete for talent by shortening their
interview cycles and making a decision as fast as possible. Often
they promise a decision in less than 10 days. Usually, they don’t
offer so competitive packages - but even that is not always true - so
they try to compete on something else.
A shorter decision cycle obviously means fewer interviews. Some-
times this approach is combined with the lack of coding interviews -
at least for senior engineers. The idea behind is that many engineers
despise the idea of implementing those coding exercises. They find
it irrelevant and even derogative to implement a linked list. Instead,
they will ask questions that help evaluate how deep you understand
the given programming language.
As this book is concentrating on C++-specific detailed knowledge
and not on coding, you’ll find it helpful in any type of interview
process.
auto and type deduction
In this chapter, we are going to learn about C++’s type deduction
rules and about how to use the auto keyword that was introduced
in C++11.

Question 1: Explain auto type


deduction!
auto type deduction is usually the same as template type deduction,
but auto type deduction assumes that a braced initializer represents
a std::initializer_list, and template type deduction doesn’t
hold such premises.
Here are a couple of examples:

1 int* ip;
2 auto aip = ip; // aip is a pointer to an integer
3 const int* cip;
4 auto acip = cip; // acip is a pointer to a const in\
5 t (the value cannot be modified, but the memory address i\
6 t points can)
7 const int* const cicp = ip;
8 auto acicp = cicp; // acicp is still a pointer t a co\
9 nst int, the constness of the pointer is discarded
10
11 auto x = 27; // (x is neither a pointer nor a r\
12 eference), x's type is int
13 const auto cx = x; // (cx is neither a pointer nor a \
14 reference), cs's type is const int
15 const auto& rx = x; // (rx is a non-universal referenc\
auto and type deduction 6

16 e), rx's type is a reference to a const int


17
18 auto&& uref1 = x; // x is int and lvalue, so uref1's\
19 type is int&
20 auto&& uref2 = cx; // cx is const int and lvalue, so \
21 uref2's type is const int &
22 auto&& uref3 = 27; // 27 is an int and rvalue, so ure\
23 f3's type is int&&
24
25 auto x3 = { 27 }; // type is std::initializer_list<i\
26 nt>, value is { 27 }
27 auto x4{ 27 }; // type is std::initializer_list<i\
28 nt>, value is { 27 }
29 // in some compilers type may be d\
30 educed as an int with a
31 // value of 27. See remarks for mo\
32 re information.
33 auto x5 = { 1, 2.0 } // error! can't deduce T for std::\
34 initializer_list<t>

As you can see if you use braced initializers, auto is forced into cre-
ating a variable of type std::initializer_list. If it can’t deduce
the type of T, the code is rejected.
We’ve also seen that auto can give us the correct type for pointers,
but in order to get a reference, we must write auto&. For consistency,
we can also write auto* in case we are expecting a pointer.
auto in a function return type or a lambda parameter implies
template type deduction, not auto type deduction.
References:

• Effective Modern C++ by Scott Meyers¹


• Modernes C++²
¹https://amzn.to/38gK5bd
²https://www.modernescpp.com/index.php/c-insights-type-deduction
auto and type deduction 7

Question 2: When can auto deduce


undesired types?

“Invisible” proxy types can cause auto to deduce the “wrong”


type for an initializer expression. One type like that is
std::vector<bool>. In case you have a function returning
such a type, and you are interested in only one bit of information
that you want to save into a variable that you declare with auto
and later you want to use an item of that vector of bools, it’s
undefined behaviour.
It sounds a little bit complex. Let’s have a look at the code.

1 std::vector<bool> foo() {
2 // ...
3 }
4
5 void bar(bool b) {
6 // ...
7 }
8
9 auto someBit = foo()[2];
10 bar(bits[2]); // Undefined behaviour

The std::vector’s [] operator returns T&. std::vector<bool> is a


specialized form of a vector containing only bits, and C++ doesn’t
allow references to bits. It’s a bit complex of what is going on
(pun intended), you can read about it more in detail in Effective
Modern C++. Long story short, if you ask for a bool, an implicit
conversion will happen. While the deduced type is something
implementation-dependent, it will be a pointer to a temporary
object. That’s something one really doesn’t want.
In such situations, it’s better either not to use auto at all, or use the
idiom that Meyers calls the the explicitly typed initializer idiom.
auto and type deduction 8

1 auto highPriority = static_cast<bool>(features(w)[5]);

Similar problems can arise when you’d want a less precise type than
returned by a function - to save some memory when you know
the range of the possible outputs. Like when a function returns a
double, but you want a float. With auto, there would be no implicit
conversion, but you can force it with the above idiom.
The idiom can also come in handy when you deal with proxy types.
References:

• Effective Modern C++ by Scott Meyers³


• Wikipedia⁴

Question 3: What are the


advantages of using auto?
auto variables must be initialized and as such, they are generally im-
mune to type mismatches that can lead to portability or efficiency
problems. auto can also make refactoring easier, and it typically
requires less typing than explicitly specified types.
auto variables mainly can improve correctness, performance, main-
tainability, and robustness. It is also more convenient to type, but
that’s its least important advantage.
Consider declaring local variables auto x = type{ expr }; when
you do want to explicitly commit to a type. It is self-documenting
to show that the code is explicitly requesting a conversion, and
it guarantees the variable will be initialized, in addition, it won’t
allow an accidental implicit narrowing conversion. Only when you
do want explicit narrowing, use ( ) instead of { }.
³https://amzn.to/38gK5bd
⁴https://en.wikipedia.org/wiki/Proxy_pattern
auto and type deduction 9

If you are worried about readability the previous technique helps,


but otherwise you shouldn’t be troubled, modern IDEs tell you the
exact types by hovering over the variable.
Regarding local variables, if you use the auto x = expr; way of
declaration, there are many advantages.

• It’s guaranteed that your variable will be initialized. If you


forgot, you’ll get an error from the compiler.
• There are no temporary objects, implicit conversion, so it is
more efficient.
• Using auto guarantees that you will use the correct type.
• In the case of maintenance, refactoring, there is no need to
update the type.
• It is the simplest way to portably spell the implementation-
specific type of arithmetic operations on built-in types. Those
types might vary from platform to platform, and it also
ensures that you cannot accidentally get lossy narrowing
conversions.
• You can omit difficult to spell types, such as lambdas, itera-
tors, etc.

References:

• Effective Modern C++ by Scott Meyers⁵


• Sutter’s Mill⁶

Question 4: What is the type of


myCollection after the following
declaration?
auto myCollection = {1,2,3};
⁵https://amzn.to/38gK5bd
⁶https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
auto and type deduction 10

The type is std::initializer_list<int>. The int part is probably


straightforward, and about std::initializer_list, well you just
have to know that with auto type deduction, if you use braces, you
have two options.
If you put nothing between the curly braces, you’ll get
a compilation error as the compiler is unable to deduce
‘std::initializer_list<auto>’ from ‘<brace-enclosed initializer
list>()’. So you don’t get an empty container, but a compilation
error instead.
If you have at least one element between the braces, the type will
be std::initializer_list.
If you wonder what this type is, you should know that it is a
lightweight proxy object providing access to an array of objects
of type const T. It is automatically constructed when:
* a braced-init-list is used to list-initialize an object, where the
corresponding constructor accepts a std::initializer_list parameter
* a braced-init-list is used on the right side of an assignment or
as a function call argument, and the corresponding assignment
operator/function accepts a std::initializer_list parameter
* a braced-init-list is bound to auto, including in a ranged for loop
Initializer lists may be implemented with a pair of pointers or
with a pointer and a length. Copying a std::initializer_list is
considered a shallow copy as it doesn’t copy the underlying objects.
References:

• C++ Reference: std::initializer_list⁷


• C++ Reference: List initialization⁸
⁷https://en.cppreference.com/w/cpp/utility/initializer_list
⁸https://en.cppreference.com/w/cpp/language/list_initialization
auto and type deduction 11

Question 5: What are trailing return


types?
Trailing return type allows the function’s return type to be declared
following the parameter list (after the ->):

1 auto getElement(const std::vector<int>& container, int in\


2 dex) const -> int;

Instead of starting with int as a return type, we put the auto


keyword at the beginning of the line and we add int at the end
as a return type after an arrow (->).
With the help of the trailing return type, we can omit the scope of
enums for example.
Let’s suppose we have this class definition:

1 class Wine {
2 public:
3 enum WineType { WHITE, RED, ROSE, ORANGE };
4
5 void setWineType(WineType wine_type);
6
7 WineType getWineType() const;
8
9 //...
10 private:
11 WineType _wine_type;
12 };

Instead of writing:
auto and type deduction 12

1 Wine::WineType Wine::getWineType() {
2 return _wine_type;
3 }

We can write:

1 auto Wine::getWineType() -> WineType {


2 return _wine_type;
3 }

At the beginning of the line, the compiler cannot know the scope,
hence we have to write Wine::WineType. Whereas when we declare
the return type at the end, the compiler already knows what we are
in the scope of Wine, so we don’t have to repeat that information.
Depending on your scope’s name, you might spare some characters,
but at least you don’t have to duplicate the class name.
Probably a more compelling reason to use trailing return types is
simplifybg function templates when the return type depends on the
argument types. More about that in the next question.
References:

• Effective Modern C++ by Scott Meyers⁹


• Sandor Dargo’s Blog¹⁰

Question 6: Explain decltype!


decltype is a keyword used to query the type of a variable or an
expression. Introduced in C++11, its primary intended use is in
generic programming, where it is often difficult, or even impossible,
to express types that depend on template parameters.
⁹https://amzn.to/38gK5bd
¹⁰https://www.sandordargo.com/blog/2018/11/07/trailing-return-type
auto and type deduction 13

Given a name or an expression, decltype tells you the name’s or the


expression’s type.
Let’s try with some variables and function calls:

1 const int&& foo();


2
3 const int bar();
4
5 int i;
6
7 decltype(foo()) x1; // type is const int&&
8 decltype(bar()) x2; // type is int
9 decltype(i) x3; // type is int

No surprise. Try with some expressions:

1 struct A {
2 double x;
3 };
4
5 const A* a;
6
7 // type of y is double (declared type)
8 decltype(a->x) y;
9
10 // type of z is const double& (lvalue expression)
11 decltype((a->x)) z = y;

Still what we wanted.


Perhaps the primary usage of decltype is declaring function tem-
plates where the function’s return type depends on its parameters’
types. A usual use-case is a simple addition: adding two values of
possibly different types can give a result of many different types,
especially when operator overloading is involved.
auto and type deduction 14

1 template <class T, class U>


2 auto add(T const& t, U const& u) -> decltype(t+u) {
3 return t+u;
4 }

In C++14, return type deduction for functions was added as a


feature, so we could simply use auto.
decltype is also useful for lambda-related types:

1 auto f = [](int a, int b) -> int {


2 return a * b;
3 };
4
5 // the type of a lambda function is unique and unnamed
6 decltype(f) g = f;

References:

• C++ Reference¹¹
• Effective Modern C++ by Scott Meyers¹²
• Simplify C++¹³

Question 7: When to use


decltype(auto)?

The decltype(auto) idiom was introduced in C++14. Like auto,


it deduces a type from its initializer, but it performs the type
deduction using the decltype rules.
¹¹https://en.cppreference.com/w/cpp/language/decltype
¹²https://amzn.to/38gK5bd
¹³https://arne-mertz.de/2017/01/decltype-declval/
auto and type deduction 15

It allows return type forwarding in generic code, as we referred to


it in the previous question. You can use auto type deduction even
for return types, and you can even return a reference like that or
you can declare your return type const:

1 auto const& Example(int const& i) {


2 return i;
3 }

On the other hand, in generic code, you have to be able to perfectly


forward a return type without knowing whether you are dealing
with a reference or a value. decltype(auto) gives you that ability:

1 template<class Fun, class... Args>


2 decltype(auto) foo(Fun fun, Args&&... args) {
3 return fun(std::forward(args)...);
4 }

If you wanted to achieve the same thing simply with auto, you
would have to declare different overloads for the same example
function above, one with the return type auto always deducing to
a pure value, and one with auto& always deducing to a reference
type.
It can also be used to declare local variables when you want to apply
the decltype type deduction rules to the initializing expression.
auto and type deduction 16

1 Widget w;
2
3 const Widget& cw = w;
4
5 auto myWidget1 = cw; // auto type deduction:
6 // myWidget1's type is Widget
7
8 decltype(auto) myWidget2 = cw; // decltype type deducti\
9 on:
10 // myWidget2's type is con\
11 st Widget&

References:

• Effective Modern C++ by Scott Meyers¹⁴


• What are some uses of decltype(auto)?¹⁵

Question 8: Which data type do you


get when you add two bools?
To be more practical, what’s the output of this code snippet:

¹⁴https://amzn.to/38gK5bd
¹⁵https://stackoverflow.com/questions/24109737/what-are-some-uses-of-decltypeauto
auto and type deduction 17

1 #include <iostream>
2
3 auto foo(bool n, bool m) {
4 return n + m;
5 }
6
7 int main() {
8 bool a = true;
9 bool b = true;
10 auto c = a + b;
11 std::cout << c << ", " << typeid(c).name() << '\n';
12 std::cout << foo(a,b) << '\n';
13 }

The answer is that both c and the return value of foo() will equal
2. So obviously their type is not bool but rather int.
In order to understand what happens, the best you can do is to
copy-paste the code to CppInsights¹⁶ that does a source-to-source
transformation. It helps you to see your source code with the eyes
of a compiler.
You’ll see this:

1 int c = static_cast<int>(a) + static_cast<int>(b);

And something very similar for foo. So both of our booleans, a


and b are promoted, they are cast to integers before they are added
together.
¹⁶https://cppinsights.io/
The keyword static and
its different usages
In this chapter our schwerpunkt, or main point of focus is on the
keyword static.

Question 9: What does a static


member variable in C++ mean?
A member variable that is declared static is allocated storage in the
static storage area, only once during the program lifetime. Given
that there is only one single copy of the variable for all objects, it’s
also called a class member.
When we declare a static member in the header file, we’re telling
the compiler about the existence of a static member variable,
but we do not actually define it (it’s pretty much like a forward
declaration in that sense). Because static member variables are not
part of class instances (they are treated similarly to global variables,
and get initialized when the program starts), you must explicitly
define the static member outside of the class, in the global scope.

1 class A {
2 static MyType s_var;
3 };
4
5 MyType A::s_var = value;

Though there are a couple of exceptions. First, when the static


member is a const integral type (which includes char and bool) or
The keyword static and its different usages 19

a const enum, the static member can be initialized inside the class
definition:

1 class A {
2 static int s_var{42};
3 };

static constexpr members can be initialized inside the class defi-


nition starting from C++17 (no out-of-line initialization required).

1 class A {
2 // this would work for any class supporting consexpr in\
3 itialization
4 static constexpr std::array<int, 3> s_array{ 1, 2, 3 };
5 };

If you are calling a static data member within a member function,


the member function should be declared as static.
It’s been already mentioned but it’s worth emphasizing that static
member variables are created when the program starts and de-
stroyed when the program ends and as such static members exist
even if no objects of the class have been instantiated.
Reference:

• C++ Reference: static¹⁷

Question 10: What does a static


member function mean in C++?
static member functions can be used to work with static member
variables in the class or to perform operations that do not require an
¹⁷https://en.cppreference.com/w/cpp/language/static
The keyword static and its different usages 20

instance of the class. Yet the actions performed in a static member


function should conceptually, semantically be strongly related to
the class. Some key points about static member functions.

• static member functions don’t have the this pointer


• static member functions cannot be virtual
• static member functions cannot access non-static members
• The const and volatile qualifiers aren’t available for static
member functions

In practice, a static member functions mean that you can call


such functions without having the class instantiated. If you have a
static void bar() on Foo class, you can call it like this Foo::bar()
without having Foo ever instantiated. (You can also call it on an
instance by the way: aFoo.bar()).
As this pointer always holds the memory address of the current
object and to call a static member you don’t need an object at all,
it cannot have a this pointer.
A virtual member is something that doesn’t relate directly to any
class, only to an instance. A “virtual function” is (by definition) a
function that is dynamically linked, i.e. the right implementaion is
chosen at runtime depending on the dynamic type of a given object.
Hence, if there is no object, there cannot be a virtual call.
Accessing a non-static member function requires that the object
has been constructed but for static calls, we don’t pass any instan-
tiation of the class. It’s not even guaranteed that any instance has
been constructed.
Once again, the const and the const volatile keywords modify
whether and how an object can be modified or not. As there is no
object…
References:
The keyword static and its different usages 21

• LearnC++: Static member functions¹⁸


• C++Reference: Static members¹⁹

Question 11: What is the static


initialization order fiasco?
The static initialization order fiasco is a subtle aspect of C++ that
many don’t know about, don’t consider or misunderstand. It’s hard
to detect as the error often occurs before main() would be invoked.
Static or global variables in one translation unit are always initial-
ized according to their definition order. On the other hand, there is
no strict order for which translation unit is initialized first.
Let’s suppose that you have a translation unit A with a static
variable sA, which depends on static variable sB from translation
unit B in order to get initialized. You have 50% chance to fail. This
is the static initialization order fiasco.
Let’s see an example for the fiasco.

1 // Logger.cpp
2
3 #include <string>
4
5 std::string theLogger = "aNiceLogger";
6
7 // KeyBoard.cpp
8
9 #include <iostream>
10 #include <string>
11
12 extern std::string theLogger;
¹⁸https://www.learncpp.com/cpp-tutorial/static-member-functions/
¹⁹https://en.cppreference.com/w/cpp/language/static
The keyword static and its different usages 22

13 std::string theKeyboard = "The Keyboard with logger: " + \


14 theLogger;
15
16 int main() {
17 std::cout << "theKeyboard: " << theKeyboard << '\n';
18 }

In this example, we have a Keyboard (driver) and a Logger. Let’s


consider that the keyboard driver needs a logger, so in case of
an error, it has somewhere to log them. Due to some reasons, we
decided that there can be one keyboard and one logger, so we made
them global. Obviously not a great design decision, but it’s a quite
common scenario and it’s also for the sake of the example.
If the keyboard is initialized sooner than the logger, there is an issue,
as you can see it in the below example:

1 sdargo@host ~/static_fiasco $ g++ -c Logger.cpp


2 sdargo@host ~/static_fiasco $ g++ -c Keyboard.cpp
3 sdargo@host ~/static_fiasco $ g++ Logger.o Keyboard.o -o \
4 LoggerThenKeyboard
5 sdargo@host ~/static_fiasco $ g++ Keyboard.o Logger.o -o \
6 KeyboardThenLogger
7
8 sdargo@host ~/static_fiasco $ ./KeyboardThenLogger
9
10 theKeyboard: The Keyboard with logger:
11
12 sdargo@host ~/static_fiasco $ ./LoggerThenKeyboard
13 theKeyboard: The Keyboard with logger: aNiceLogger

This is the static initialization order fiasco in action.


Beware that dependencies on static variables in different transla-
tion units are a code smell and in fact, should be a good reason for
refactoring.
The keyword static and its different usages 23

References:

• C++ FAQ²⁰
• ModernesC++²¹

Question 12: How to solve the static


initialization order fiasco?
As a reminder, static or global variables in one translation unit are
always initialized according to their definition order. On the other
hand, there is no strict order for which translation unit is initialized
first.
In case, you have a translation unit A with a static variable sA,
which depends on static variable sB from translation unit B in order
to get initialized, you have 50% chance to fail. This is the static
initialization order fiasco.
Dependencies on static variables in different translation units are
code smells and in fact, should be a good reason for refactoring.
Hence the most straightforward way to solve this problem is to
remove such dependencies.
I recite here our example from yesterday.

²⁰http://www.cs.technion.ac.il/users/yechiel/c++-faq/static-init-order.html
²¹https://www.modernescpp.com/index.php/c-20-static-initialization-order-fiasco
The keyword static and its different usages 24

1 // Logger.cpp
2
3 #include <string>
4
5 std::string theLogger = "aNiceLogger";
6
7 // KeyBoard.cpp
8
9 #include <iostream>
10 #include <string>
11
12 extern std::string theLogger;
13 std::string theKeyboard = "The Keyboard with logger: " + \
14 theLogger;
15
16 int main() {
17 std::cout << "theKeyboard: " << theKeyboard << '\n';
18 }

There is a 50-50% chance of failure. If the compilation unit Log-


ger.cpp gets initialized first, we are good. If the keyboard, not so.
Probably the simplest solution is to replace theLogger variable in
Logger.cpp with a function like this:

1 std::string theLogger() {
2 static std::string aLogger = "aNiceLogger";
3 return aLogger;
4 }

Then in the Keyboard.cpp we just have to make sure that we use


extern on the function and we call the function later on instead
of referencing the variable. This works because the local static
std::string aLogger variable will be initialized the first time that
theLogger() function is called. Hence, it’s guaranteed that when
theKeyboard is constructed, theLogger will be initialized.
The keyword static and its different usages 25

You might face other issues if theLogger would be used during pro-
gram exit by another static variable after theLogger got constructed.
Again, dependencies on static variables in different translation
units are code smells…
Starting from C++20, the static initialization order fiasco can be
solved with the use of constinit. In this case, the static variable will
be initialized at compile-time, before any linking. You can check
that solution here²².
References:

• C++ FAQ²³
• ModernesC++²⁴

²²https://www.modernescpp.com/index.php/c-20-static-initialization-order-fiasco
²³http://www.cs.technion.ac.il/users/yechiel/c++-faq/static-init-order.html
²⁴https://www.modernescpp.com/index.php/c-20-static-initialization-order-fiasco
Polymorphism,
inheritance and virtual
functions
For the next sixteen questions, we’ll focus on polymorphism, inher-
itance, virtual functions and similar topics.

Question 13: What is the difference


between function overloading and
function overriding?
Function overriding is a term related to polymorphism. If you
declare a virtual function in a base class with a certain implemen-
tation, in a derived class you can override its behaviour using the
very same signature. If the virtual keyword is missing in the base
class, it’s still possible to create a function with the same signature
in the derived class, but it doesn’t override it. Since C++11 there is
also the override specifier which helps you to make sure that you
didn’t mistakenly fail to override a base class function. You can find
more details here²⁵.
Function overriding enables you to provide specific implementa-
tions in derived classes for functions that are already defined in the
base class.
On the other hand, overloading has nothing to do with poly-
morphism. When you have two functions with the same name,
²⁵
Polymorphism, inheritance and virtual functions 27

same return type, but different numbers or types of parameters, or


qualifiers.
Here is an example for overloading based on parameters

1 void myFunction(int a);


2
3 void myFunction(double a);
4
5 void myFunction(int a, int b);

And here is another example based on qualifiers:

1 class MyClass {
2 public:
3 void doSomething() const;
4 void doSomething();
5 };

It is even possible to overload a function based on whether the


actual instance is an lvalue or an rvalue.

1 class MyClass {
2 public:
3 // ...
4 void doSomething() &; // used when *this is a lvalue
5 void doSomething() &&; // used when *this is a rvalue
6 };

You can learn more about this here²⁶.


References

• Why to use the override specifier in C++ 11?²⁷


• How to use ampersands in C++²⁸
²⁶http://sandordargo.com/blog/2018/11/25/override-r-and-l0-values
²⁷http://sandordargo.com/blog/2018/07/05/cpp-override
²⁸http://sandordargo.com/blog/2018/11/25/override-r-and-l0-values
Polymorphism, inheritance and virtual functions 28

Question 14: What is a virtual


function?
A virtual function is used to replace, in other words, to override
the implementation provided by the base class.
The code in the derived class is always called whenever the object
is actually of the derived class, even if the object is accessed by a
base class pointer rather than a derived one.

1 #include <iostream>
2
3 class Car {
4 public:
5 virtual void printName() {
6 std::cout << "Car\n";
7 }
8 };
9
10 class SUV: public Car {public:
11 virtual void printName() override {
12 std::cout << "SUV\n";
13 }
14 };
15
16 int main() {
17 Car* car = new SUV();
18 car->printName();
19 }
20 /*
21 SUV
22 */

A virtual function is a member function that is present in the base


class and might be redefined by the derived class(es). When we
Polymorphism, inheritance and virtual functions 29

use the same function name both in the base and derived classes,
the function in the base class must be declared with the keyword
virtual - otherwise it’s not overriden just shadowed.

When the function is made virtual, then C++ determines at run-


time which function is to be called based on the type of the object
pointed by the base class pointer. Thus, by making the base class
pointer point to different objects, we can execute different versions
of the virtual functions.
Some rules for virtual function:

• They are always member functions


• They cannot be static
• They can be a friend of another class
• C++ does not contain virtual constructors but can have a
virtual destructor

In fact, if you want to allow other classes to inherit from a given


class, you should always make the destructor virtual, otherwise,
you can easily have undefined behaviour.
Reference:

• C++ Reference: virtual function specifier²⁹

Question 15: What is the override


keyword and what are its
advantages?
The override specifier will tell both the compiler and the reader
that the function where it is used is actually overriding a method
from its base class.
²⁹https://en.cppreference.com/w/cpp/language/virtual
Polymorphism, inheritance and virtual functions 30

It tells the reader that “this is a virtual method, that is overriding


a virtual method of the base class.”
Use it correctly and you see no effect:

1 class Base {
2 virtual void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // OK: Derived::foo overrides Base\
7 ::foo
8 };

But it will help you revealing problems with constness:

1 class Base {
2 virtual void foo();
3 void bar();
4 };
5
6 class Derived : Base {
7 void foo() const override; // Error: Derived::foo does \
8 not override Base::foo\n
9 // It tries to override Base\
10 ::foo const that doesn't exist
11 };

Let’s not forget that in C++, methods are non-virtual by default.


If we use override, we might find that there is nothing to override.
Without the override specifier we would just simply create a brand
new method. No more base methods are forgotten to be declared
as virtual if you use consistently the override specifier.
\n
Polymorphism, inheritance and virtual functions 31

1 class Base {
2 void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // Error: Base::foo is not virtual
7 };

We should also keep in mind that when we override a method - with


or without the override specifier - no conversions are possible:

1 class Base {
2 public:
3 virtual long foo(long x) = 0;
4 };
5
6 class Derived: public Base {
7 public:
8 // error: 'long int Derived::foo(int)' marked override,\
9 but does not override\n
10 long foo(int x) override {
11 // ...
12 }
13 };

In my opinion, using the override specifier from C++11 is part of


clean coding principles. It reveals the author’s intentions, it makes
the code more readable and helps to identify bugs at build time. Use
it without moderation!
References

• C++ Reference: override specifier³⁰


• Why to use the override specifier in C++ 11?³¹
³⁰https://en.cppreference.com/w/cpp/language/override
³¹https://www.sandordargo.com/blog/2018/07/05/cpp-override
Polymorphism, inheritance and virtual functions 32

Question 16: Explain the concept of


covariant return types and show a
use-case where it comes in handy!
Using covariant return types for a virtual function and for all its
overridden versions means that you can replace the original return
type with something narrower, in other words, with something
more specialized.
Let’s say you have a CarFactoryLine producing Cars. The special-
ization of these factory lines might produce SUVs, SportsCars, etc.
How do you represent it in code? The obvious way is still having
the return type as a Car pointer.

1 class CarFactoryLine {
2 public:
3 virtual Car* produce() {
4 return new Car{};
5 }
6 };
7
8 class SUVFactoryLine : public CarFactoryLine {
9 public:
10 virtual Car* produce() override {
11 return new SUV{};
12 }
13 };

This way, getting an SUV* requires a dynamic cast.


Polymorphism, inheritance and virtual functions 33

1 SUVFactoryLine sf;
2 Car* car = sf.produce();
3 SUV* suv = dynamic_cast<SUV*>(car);

Instead, we directly return an SUV*

1 class Car {
2 public:
3 virtual ~Car() = default;
4 };
5
6 class SUV : public Car {};
7
8 class CarFactoryLine {
9 public:
10 virtual Car* produce() {
11 return new Car{};
12 }
13 };
14
15 class SUVFactoryLine : public CarFactoryLine {
16 public:
17 virtual SUV* produce() override {
18 return new SUV{};
19 }
20 };

So that you can simply do this:

1 SUVFactoryLine sf;
2 SUV* car = sf.produce();

In C++, in a derived class, in an overridden function, you don’t have


to return the same type as in the base class, but you can return a
covariant return type. In other words, you can replace the original
Polymorphism, inheritance and virtual functions 34

type with a “narrower” one, in other words, with a more specified


data type.
References:

• Covariant return types³²


• How to Return a Smart Pointer AND Use Covariance³³

Question 17: What is virtual


inheritance in C++ and when should
you use it?
Virtual inheritance is a C++ technique that ensures only one copy of
a base class’s member variables is inherited by grandchild derived
classes. Without virtual inheritance, if two classes B and C inherit
from class A, and class D inherits from both B and C, then D will
contain two copies of A’s member variables: one via B, and one via
C. These will be accessible independently, using scope resolution.
Instead, if classes B and C inherit virtually from class A, then
objects of class D will contain only one set of the member variables
from class A.
As you probably guessed, this technique is useful when you have
to deal with multiple inheritance and you can solve the infamous
diamond inheritance.
In practice, virtual base classes are most suitable when the classes
that derive from the virtual base, and especially the virtual base
itself, are pure abstract classes. This means the classes above the
“join class” (the one at the bottom) have very little if any data.
Consider the following class hierarchy to represent the diamond
problem, though not with pure abstracts.
³²https://www.sandordargo.com/blog/2020/08/19/covariant-return-types
³³https://www.fluentcpp.com/2017/09/12/how-to-return-a-smart-pointer-and-use-
covariance/
Polymorphism, inheritance and virtual functions 35

1 struct Person {
2 virtual ~Person() = default;
3 virtual void speak() {}
4 };
5
6 struct Student: Person {
7 virtual void learn() {}
8 };
9
10 struct Worker: Person {
11 virtual void work() {}
12 }; // A teaching assistant is both a worker and a student
13
14 struct TeachingAssistant: Student, Worker {};
15
16 int main() {
17 TeachingAssistant aTeachingAssistant;
18 }

As declared above, a call to aTeachingAssistant.speak() is am-


biguous because there are two Person (indirect) base classes in
TeachingAssistant, so any TeachingAssistant object has two dif-
ferent Person base class subobjects. So an attempt to directly bind
a reference to the Person subobject of a TeachingAssistant object
would fail, since the binding is inherently ambiguous:

1 TeachingAssistant aTeachingAssistant;
2 Person& aPerson = aTeachingAssistant; // error: which Pe\
3 rson subobject should
4 // a TeachingAssist\
5 ant cast into,
6 // a Student::Perso\
7 n or a Worker::Person?

To disambiguate, one would have to explicitly convert


aTeachingAssistant to either base class subobject:
Polymorphism, inheritance and virtual functions 36

1 TeachingAssistant aTeachingAssistant;
2 Person& student = static_cast<Student&>(aTeachingAssistan\
3 t);
4 Person& worker = static_cast<Worker&>(aTeachingAssistant);

In order to call speak(), the same disambigua-


tion, or explicit qualification is needed: static_-
cast<Student&>(aTeachingAssistant).speak() or
static_cast<Worker&>(aTeachingAssistant).speak() or
alternatively aTeachingAssistant.Student::speak() and
aTeachingAssistant.Worker::speak(). Explicit qualification
not only uses an easier, uniform syntax for both pointers and
objects but also allows for static dispatch, so it would arguably be
the preferable method.
In this case, the double inheritance of Person is probably unwanted,
as we want to model that the relation (TeachingAssistant is a
Person) exists only once; that a TeachingAssistant is a Student
and is a Worker does not imply that it is a Person twice (unless
the TeachingAssistant is schizophrenic): a Person base class cor-
responds to a contract that TeachingAssistant implements (the “is
a” relationship above really means “implements the requirements
of”), and a TeachingAssistant only implements the Person contract
once.
The real-world meaning of “only once” is that TeachingAssistant
should have only one way of implementing speak, not two
different ways, depending on whether the Student view of
the TeachingAssistant is eating, or the Worker view of the
TeachingAssistant. (In the first code example we see that speak()
is not overridden in either Student or Worker, so the two Person
subobjects will actually behave the same, but this is just a
degenerate case, and that does not make a difference from the C++
point of view.)
If we introduce virtual to our inheritance as such, our problems
disappear.
Polymorphism, inheritance and virtual functions 37

1 struct Person {
2 virtual ~Person() = default;
3 virtual void speak() {}
4 };
5
6 // Two classes virtually inheriting Person:
7 struct Student: virtual Person {
8 virtual void learn() {}
9 };
10
11 struct Worker: virtual Person {
12 virtual void work() {}
13 };
14
15 // A teaching assistant is still a student and the worker
16 struct TeachingAssistant: Student, Worker {};

Now we can easily call speak().


The Person portion of TeachingAssistant::Worker is now the same
Person instance as the one used by TeachingAssistant::Student,
which is to say that a TeachingAssistant has only one,
shared, Person instance in its representation and so a call
to TeachingAssistant::speak is unambiguous. Additionally,
a direct cast from TeachingAssistant to Person is also
unambiguous, now that there exists only one Person instance
which TeachingAssistant could be converted to.
This can be done through vtable pointers. Without going into
details, the object size increases by two pointers, but there is only
one Person object behind and no ambiguity.
You must use the virtual keyword in the middle level of the
diamond. Using it at the bottom doesn’t help.
References:

• Sandor Dargo’s Blog: What is virtual inheritance in C++ and


Polymorphism, inheritance and virtual functions 38

when should you use it?³⁴


• C++ Core Guidelines³⁵
• Wikipedia - Virtual inheritance³⁶

Question 18: Should we always use


virtual inheritance? If yes, why? If
not, why not?
The answer is definitely no, we shouldn’t use virtual inheritance
all the time. According to an idiomatic answer, one of the key
C++ characteristics is that you should only pay for what you use.
And if you don’t need to solve the problems addressed by virtual
inheritance, you should rather not pay for it.
Virtual inheritance is almost never needed. It addresses the dia-
mond inheritance problem that we saw just yesterday. It can only
happen if you have multiple inheritance, and in case you can avoid
it, you don’t have the problem to solve. In fact, many languages
don’t even have this feature.
So let’s see the main drawbacks.
Virtual inheritance causes troubles with object initialization and
copying. Since it is the “most derived” class that is responsible
for these operations, it has to be familiar with all the intimate
details of the structure of base classes. Due to this, a more complex
dependency appears between the classes, which complicates the
project structure and forces you to make some additional revisions
in all those classes during refactoring. All this leads to new bugs
and makes the code less readable.
Troubles with type conversions may also be a source of bugs. You
can partly solve the issues by using the expensive dynamic_cast
³⁴https://www.sandordargo.com/blog/2020/12/23/virtual-inheritance
³⁵https://isocpp.org/wiki/faq/multiple-inheritance
³⁶https://en.wikipedia.org/wiki/Virtual_inheritance
Polymorphism, inheritance and virtual functions 39

wherever you used to use static_cast. Unfortunately, dynamic_-


cast is much slower, and if you have to use it too often in your code,
it means that your project’s architecture has quite some room for
improvement.
References:

• Sandor Dargo’s Blog: What is virtual inheritance in C++ and


when should you use it?³⁷
• C++ Core Guidelines³⁸
• Wikipedia - Virtual inheritance³⁹

Question 19: What’s the output of


the following sample program? Is
that what you’d expect? Why? Why
not?
1 #include <iostream>
2 #include <memory>
3
4 class Animal {
5 public:
6 ~Animal() = default;
7 virtual void eat(int quantity) {
8 std::cout << "Animal eats " << quantity << std::endl;
9 }
10
11 void speak() {
12 std::cout << "Animal speaks" << std::endl;
13 }
³⁷https://www.sandordargo.com/blog/2020/12/23/virtual-inheritance
³⁸https://isocpp.org/wiki/faq/multiple-inheritance#mi-disciplines
³⁹https://en.wikipedia.org/wiki/Virtual_inheritance
Polymorphism, inheritance and virtual functions 40

14 };
15
16 class Dog : public Animal {
17 public:
18 void eat(unsigned int quantity) {
19 std::cout << "Dog eats " << quantity << std::endl;
20 }
21
22 void speak() {
23 std::cout << "Dog speaks" << std::endl;
24 }
25 };
26
27 int main() {
28 Dog d;
29 d.speak();
30 d.eat(42u);
31
32 std::unique_ptr<Animal> a = std::make_unique<Dog>();
33 a->speak();
34 a->eat(42u);
35 }

If you copied the above piece of program into a compiler you could
easily get the solution. I hope that first you tried to reason about it.

1 Dog speaks
2 Dog eats 42
3 Animal speaks
4 Animal eats 42

If this is what you expected, congratulations! Most probably then


you also know why.
If you didn’t figure it out and the above solution surprised you,
don’t worry. Check the code once more and read on.
Polymorphism, inheritance and virtual functions 41

There are two small mistakes in the original code that are so easy
to ignore, and until C++11 you only had your eyes or your code
reviewers’ eyes to help you with.
But since C++11, you have the override specifier⁴⁰. It helps you
to explicitly mark that a function in a derived class is supposed to
override something from its base.

1 std::unique_ptr<Animal> a = std::make_unique<Dog>();
2 a->speak();

When we write such code, we expect a to invoke the behaviour


defined in the derived class, in Dog. Yet we fall back to Animal
behaviour.
As the override is missing from the Dog method signatures, let’s
add them, and recompile.

1 #include <iostream>
2 #include <memory>
3
4 class Animal {
5 public:
6 ~Animal() = default;
7 virtual void eat(int quantity) {
8 std::cout << "Animal eats " << quantity << std::endl;
9 }
10
11 void speak() {
12 std::cout << "Animal speaks" << std::endl;
13 }
14 };
15
16 class Dog : public Animal {
17 public:
⁴⁰https://www.sandordargo.com/blog/2018/07/05/cpp-override
Polymorphism, inheritance and virtual functions 42

18 void eat(unsigned int quantity) override {


19 std::cout << "Dog eats " << quantity << std::endl;
20 }
21
22 void speak() override {
23 std::cout << "Dog speaks" << std::endl;
24 }
25 };
26
27 int main() {
28 Dog d;
29 d.speak();
30 d.eat(42u);
31
32 std::unique_ptr<Animal> a = std::make_unique<Dog>();
33 a->speak();
34 a->eat(42u);
35 }

According to the error messages, neither Dog::eat nor Dog::speak


overrides anything.
In the first case, it’s because in the base class the parameter is a sim-
ple signed int, while in the derived class we have a function with
the same name, same return type, but with a different parameter:
an unsigned int. We have to match the two, in case we are looking
for polymorphic behaviour.
In the other case, we simply forgot to add the virtual keyword to
the signature in the base class, hence it cannot be overridden.
Fix these two mistakes and you’ll get the behaviour that we would
commonly expect.
Polymorphism, inheritance and virtual functions 43

1 #include <iostream>
2 #include <memory>
3
4 class Animal {
5 public:
6 ~Animal() = default;
7 virtual void eat(int quantity) {
8 std::cout << "Animal eats " << quantity << std::endl;
9 }
10
11 virtual void speak() {
12 std::cout << "Animal speaks" << std::endl;
13 }
14 };
15
16 class Dog : public Animal {
17 public:
18 void eat(int quantity) override {
19 std::cout << "Dog eats " << quantity << std::endl;
20 }
21
22 void speak() override {
23 std::cout << "Dog speaks" << std::endl;
24 }
25 };
26
27 int main() {
28 Dog d;
29 d.speak();
30 d.eat(42u);
31
32 std::unique_ptr<Animal> a = std::make_unique<Dog>();
33 a->speak();
34 a->eat(42u);
35 }
Polymorphism, inheritance and virtual functions 44

The takeaway is that you should always use the override specifier
for functions that are meant to override base class functions so that
you can catch these subtle bugs already at compile time.
Reference::

• Sandor Dargo’s Blog: the override specifier⁴¹

Question 20: Can you access the


public and protected members and
functions of a base class if you have
private inheritance?
The access specifier of the inheritance doesn’t affect the inheritance
of the implementation. The implementation is always inherited
based on the function’s access level. The inheritance’s access spec-
ifier only affects the accessibility of the class interface.
This means that all the public and protected variables and functions
will be useable from the derived class even when you use private
inheritance.
On the other hand, those public and protected elements of the base
class will not be accessible from the outside through the derived
class.
A grandchild of a base class, if its parent inherited privately from
the base (the grandparent…), won’t have any access to the base’s
members and functions. Not even if they were originally protected
or even public.

⁴¹https://www.sandordargo.com/blog/2018/07/05/cpp-override
Polymorphism, inheritance and virtual functions 45

1 #include <iostream>
2
3 class Base {
4 public:
5 Base() = default;
6 virtual ~Base() = default;
7 virtual int x() {
8 std::cout << "Base::x()\n";
9 return 41;
10 }
11
12 protected:
13 virtual int y() {
14 std::cout << "Base::y()\n";
15 return 42;
16 }
17 };
18
19 class Derived : private Base {
20 public:
21 int x() override {
22 std::cout << "Derived::x()\n";
23 return Base::y();
24 }
25 };
26
27 class SoDerived : public Derived {
28 public:
29 int x() override {
30 std::cout << "SoDerived::x()\n";
31 return Base::y();
32 }
33 };
34
35 int main() {
Polymorphism, inheritance and virtual functions 46

36 SoDerived* p = new SoDerived();


37 std::cout << p->x() << std::endl;
38 }
39 /*
40 main.cpp: In member function 'virtual int SoDerived::x()':
41 main.cpp:31:12: error: 'class Base Base::Base' is private\
42 within this context
43 31 | return Base::y();
44 | ^~~~
45 main.cpp:19:7: note: declared private here
46 19 | class Derived : private Base {
47 | ^~~~~~~
48 main.cpp:31:19: error: 'Base' is not an accessible base o\
49 f 'SoDerived'
50 31 | return Base::y();
51 | ~~~~~~~^~
52 */

References::

• Sandor Dargo’s Blog: The quest of private inheritance in


C++⁴²
• C++ Core Guidelines: Inheritance — private and protected
inheritance⁴³

Question 21: What is private


inheritance used for?
So first, let’s have a small reminder on what private inheritance is.

The access specifier of the inheritance doesn’t affect the


inheritance of the implementation. The implementation
⁴²https://www.sandordargo.com/blog/2020/04/01/private-inheritanc-vs-composition
⁴³https://isocpp.org/wiki/faq/private-inheritance
Polymorphism, inheritance and virtual functions 47

is always inherited based on the function’s access level.


The inheritance’s access specifier only affects the
accessibility of the class interface.

This means that all the public and protected variables and functions
will be useable from the derived class even when you use private
inheritance.
On the other hand, those public and protected elements of the base
class will not be accessible from the outside through the derived
class.
When can this be useful?
We probably all learnt that inheritance is there for expressing is-a
relationships, right?
If there is Car class inheriting from Vehicle, we can all say that a
Car is a Vehicle. Then Roadster class inherits from Car, it’s still a
Vehicle having access to all Vehicle member( function)s.

But what if that inheritance between Vehicle and Car was private?
Then that little shiny red Roadster will not have access to the
interface of Vehicle, even if it publicly inherits from Car in the
middle.
We simply cannot call it an is-a relationship anymore.
It’s a has-a relationship. Derived classes, in this specific example
Car, will have access to the Base class (e.g.Vehicle) and expose
it based on the access level, protected or private. Well, this latter
means that it’s not exposed. It serves as a private member.
In the case of protected, you might argue that well, Roadster still
have access to Vehicle, that is true.
But you cannot create a Roadster as a Vehicle when you use non-
public inheritance. This line will not compile.
Polymorphism, inheritance and virtual functions 48

1 Vehicle* p = new Roadster();

Just to repeat it, non-public inheritance in C++ expresses a has-a


relationship.
Just like composition.
So if we want to keep the analogy of cars, we can say that a Car
can privately inherit from the hypothetical Engine class - while
it still publicly inherits from Vehicle. And with this small latter
addition of multiple inheritance, you probably got the point, why
composition is easier to maintain than private inheritance.
But even if you have no intention of introducing an inheritance tree,
I think private inheritance is not intuitive and it’s so different from
most of the other languages that it’s simply disturbing to use it. It’s
not evil at all, it’ll be just more expensive to maintain.
That’s exactly what you can find in the C++ Core guidelines⁴⁴.

Use composition when you can, private inheritance


when you have to.

But when do you have to use private inheritance?


According to the Core Guidelines, you have a valid use-case when
the following conditions apply:

• The derived class has to make calls to (non-virtual) functions


of the base
• The base has to invoke (usually pure-virtual) functions from
the derived

References::
⁴⁴https://isocpp.org/wiki/faq/private-inheritance
Polymorphism, inheritance and virtual functions 49

• Sandor Dargo’s Blog: The quest of private inheritance in


C++⁴⁵
• C++ Core Guidelines: Inheritance — private and protected
inheritance⁴⁶

Question 22: Can you call a virtual


function from a constructor or a
destructor?
Technically you can, the code will compile. But the code can be
misleading and it can even lead to undefined behaviour.
Attempting to call a derived class function from a base class under
construction is dangerous.

1 #include <iostream>
2
3 class Base {
4 public:
5 Base() {
6 foo();
7 }
8 protected:
9 virtual void foo() {
10 std::cout << "Base::foo\n";
11 }
12 };
13
14 class Derived : public Base {
15 public:
16 Derived() {}
⁴⁵https://www.sandordargo.com/blog/2020/04/01/private-inheritanc-vs-composition
⁴⁶https://isocpp.org/wiki/faq/private-inheritance
Polymorphism, inheritance and virtual functions 50

17 void foo() override {


18 std::cout << "Derived::foo\n";
19 }
20 };
21
22 int main() {
23 Derived d;
24 }

The output is simply Base::foo:

• By contract, the derived class constructor starts by calling the


base class constructor.
• The base class constructor calls the base member function and
not the one overridden in the child class, which is confusing
for the child class’ developer.

In case, the virtual method is also a pure virtual method, you have
undefined behaviour. If you’re lucky, at link time you’ll get some
errors.
What’s the solution?
The simplest probably is just to fully reference the function that
you’ll call:

1 Base() {
2 Base::foo();
3 }

From the Base class, you can call only the Base class’ function.
When you’re in the Derived class, you can decide which class’
version do you want to call.
A more elegant solution is if you wrap the virtual functions in
non-virtual functions and from the constructors/destructors you
only use the non-virtual wrappers.
Polymorphism, inheritance and virtual functions 51

For full source code, check out the references.


References:

• SEI CERT: OOP50-CPP. Do not invoke virtual functions from


constructors or destructors⁴⁷
• SonarSource: RPSEC-1699⁴⁸

Question 23: What role does a virtual


destructor play?
The destructor of an object is executed when the object goes out of
scope. It doesn’t take arguments, it cannot be overloaded, but it can
be virtual.
Using virtual destructors, you can destroy objects without know-
ing their types — the correct destructor for the object is invoked
using the virtual function mechanism. Destructors can also be
declared as pure virtual functions for abstract classes.
A derived class’ destructor (whether or not you explicitly define
one) automatically invokes the destructors for base class subobjects.
Base classes are destructed after member objects. In the event
of multiple inheritance, direct base classes are destructed in the
reverse order of their appearance in the inheritance list.
But we still don’t know why it’s needed to declare a destructor
virtual.

It’s because if you delete an object of a derived class through a


pointer to its base class that has a non-virtual destructor, the
behaviour is undefined. Instead, the base class should destructor
should be declared as virtual.
⁴⁷https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP50-CPP.+Do+not+invoke+
virtual+functions+from+constructors+or+destructors
⁴⁸https://rules.sonarsource.com/cpp/RSPEC-1699
Polymorphism, inheritance and virtual functions 52

Making base class destructor virtual guarantees that the object of a


derived class is destructed properly, i.e. both base class and derived
class destructors are called. As a guideline, any time you have a
virtual function in a class, you should immediately add a virtual
destructor (even if it does nothing). This way, you ensure against
any surprises later.
References:

• C++ Core guidelines⁴⁹


• docs.microsoft.com⁵⁰
• SEI CERT C++ Coding Standard⁵¹

Question 24: Can we inherit from a


standard container (such as
std::vector)? If so what are the
implications?
The standard containers declare their constructors as public and
non-final, so yes it is possible to inherit from them. In fact, it’s
a well-known and used technique to benefit from strongly typed
containers.

1 class Squad : public std::vector {


2 using std::vector::vector;
3 // ...
4 };
⁴⁹https://isocpp.org/wiki/faq/dtors#calling-base-dtor
⁵⁰https://docs.microsoft.com/en-us/cpp/cpp/destructors-cpp?view=vs-2019
⁵¹https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP52-CPP.+Do+not+delete+a+
polymorphic+object+without+a+virtual+destructor
Polymorphism, inheritance and virtual functions 53

It’s simple, it’s readable, yet you’ll find a lot of people at different
forums who will tell you that this is the eighth deadly sin and if
you are a serious developer you should avoid it at all costs.
Why do they say so?
There are two main arguments. One is that algorithms and contain-
ers are well-separated concerns in the STL. The other one is about
the lack of virtual destructors.
But are these valid concerns?
They might be. It depends.
Let’s start with the one about the lack of a virtual destructor. It
seems more practical.
Indeed, the lack of a virtual destructor might lead to undefined
behaviour and a memory leak. Both can be serious issues, but the
undefined behaviour is worse because it can not just lead to crashes
but even to difficult to detect memory corruption eventually lead-
ing to strange application behaviour.
But the lack of virtual destructor doesn’t lead to undefined be-
haviour and memory leak by default, you have to use your derived
class in such a way.
If you delete an object through a pointer to a base class that has
a non-virtual destructor, you have to face the consequences of
undefined behaviour. Plus if the derived object introduces new
member variables, you’ll also have some nice memory leak. But
again, that’s the smaller problem.
On the other hand, this also means that those who rigidly oppose
inheriting from std::vector - or from any class without a virtual
destructor - because of undefined behaviour and memory leaks, are
not right.
If you know what you are doing, and you only use this inheritance
to introduce a strongly typed vector, not to introduce polymorphic
behaviour and additional states to your container, it is perfectly fine
Polymorphism, inheritance and virtual functions 54

to use this technique. You simply have to respect the limitations,


though probably this is not the best strategy to use in the case of a
public library. But more on that just in a second.
So the other main concern is that you might mix containers and
algorithms in your new object. It’s bad because the creators of the
STL said so. And so what? Alexander Stepanov⁵² who originally
designed the STL and the others who have been later contributed
to it are smart people and there is a fair chance that they are better
programmers than most of us. They designed functions, objects that
are widely used in the C++ community. I think it’s okay to say that
they are used by everyone.
Most probably we are not working under such constraints, we are
not preparing something for the whole C++ community. We are
working on specific applications with very strict constraints. Our
code will not be reused as such. Never. Most of us don’t work on
generic libraries, we work on one-off business applications.
As long as we keep our code clean (whatever it means), it’s perfectly
fine to provide a non-generic solution.
As a conclusion, we can say that for application usage, inheriting
from containers in order to provide strong typing is fine, as long as
you don’t start to play with polymorphism.
Reference:

• Sandor Dargo’s blog: Strong types for containers⁵³


⁵²https://en.wikipedia.org/wiki/Alexander_Stepanov
⁵³https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
Polymorphism, inheritance and virtual functions 55

Question 25: What does a strong


type mean and what advantages
does it have?
A strong type carries extra information, a specific meaning through
its name⁵⁴. While you can use booleans or strings everywhere, the
only way they carry can carry meaning is tthrough the name of the
variables.
The main advantages of strong typing are readability and safety.
If you look at this function signature, perhaps you think it’s alright:

1 Car::Car(unit32_t horsepower, unit32_t numberOfDoors,


2 bool isAutomatic, bool isElectric);

It has relatively good names, so what is the issue?


Let’s look at a possible instantiation.

1 auto myCar{Car(96, 4, false, true)};

Yeah, what?
God knows…
And you too! But only if you take your time to actually look up
the constructor and do the mind mapping. Some IDEs can help you
visualizing parameter names, like if they were Python-style named
parameters, but you shouldn’t rely on that.
Of course, you could name the variables as such:

⁵⁴https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/
Polymorphism, inheritance and virtual functions 56

1 constexpr unit32_t horsepower = 96;


2 constexpr unit32_t numberOfDoors = 4;
3 constexpr bool isAutomatic = false;
4 constexpr bool isElectric = false;
5
6 auto myCar = Car{horsepower, numberOfDoors,
7 isAutomatic, isElectric};

Now you understand right away which variable represents what.


You have to look a few lines upper to actually get the values, but
everything is in sight. On the other hand, this requires willpower.
Discipline. You cannot enforce it. Well, you can be a thorough code
reviewer, but you won’t catch every case and anyway, you won’t
be there all the time.
Strong typing is there to help you!
Imagine the signature as such:

1 Car::Car(Horsepower hp, DoorsNumber numberOfDoors,


2 Transmission transmission, Fuel fuel);

Now the previous instantiation could look like this:

1 auto myCar{Car{Horsepower{98u}}, DoorsNumber{4u},


2 Transmission::Automatic, Fuel::Gasoline};

This version is longer and more verbose than the original version -
which was quite unreadable -, but much shorter than the one where
we introduced well-named temporary variables for each parameter.
So one advantage of strong typing is readability and one other
is safety. It’s much harder to mix up values. In the previous
examples, you could have easily mixed up door numbers with
performance, but by using strong typing, that would actually lead
to a compilation error.
References:
Polymorphism, inheritance and virtual functions 57

• Fluent Cpp⁵⁵
• SandorDargo’s Blog⁵⁶
• Correct by Construction: APIs That Are Easy to Use and Hard
to Misuse - Matt Godbolt⁵⁷

Question 26: Explain short-circuit


evaluation
What’s the output of the following piece of code and why?

1 #include <iostream>
2
3 bool a() {
4 std::cout << "a|";
5 return false;
6 }
7
8 bool b() {
9 std::cout << "b|";
10 return true;
11 }
12
13 int main() {
14 if (a() && b()) {
15 std::cout << "main";
16 }
17 }

The output will simply be a|.


⁵⁵https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/
⁵⁶https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
⁵⁷https://www.youtube.com/watch?v=nLSm3Haxz0I
Polymorphism, inheritance and virtual functions 58

In main(), in the if statement, the first subexpression a() is called


and it prints a|. As it’s evaluated to false, and therefore the whole
expression cannot be true, the second subexpression (b()) is not
executed.
In C++, like in many other programming languages there is short-
circuit evaluation meaning that once it’s for sure that the whole
condition cannot be fulfilled, the evaluation stops.
As the two subexrepssions are joined with &&, both sides must be
true in order to have the full expression true:

a() b() a() && b()


false false false
false true false
true false false
true true true

In case, a() is not true, the rest of the expression is not evaluated
to save some precious CPU cycles.
This short-circuiting is also a reason - besides sheer readability -
, why calls in a condition should not have side effects (changing
states of an object/variable). When a boolean expression is evalu-
ated it should simply return a boolean. The caller cannot be sure
whether it would be evaluated.
I know this sounds simple and probably we all learnt this at
the beginning of our C++ career. But there is something else to
remember. When you define the logical operators for your own
class, so when you overload for example operator&&, you lose short-
circuiting.
In case you have something like myObjectA && myObjectB, both sides
will be evaluated, even if the first one evaluates to false.
The reason is that before calling the overloaded operator&&, both
the left-hand-side and the right-hand-side arguments of the over-
loaded function are evaluated. A function call is a sequence-point
Polymorphism, inheritance and virtual functions 59

and therefore all the computations and the side-effects are complete
before making the function call. This is an eager strategy.
References:

• Cpp Truths⁵⁸
• StackOverflow⁵⁹

Question 27: What is a destructor


and how can we overload it?
A destructor is a special member function of a class. It has the same
name as the class and it is also prefixed with a tilde symbol (e.g.
∼MyClass). If available, it is executed automatically whenever an
object goes out of scope.
A destructor has no parameters, it cannot be const, volatile or
static and just like constructors, it has no return type.

By default, it is generated by the compiler, but you have to pay


attention as the rule of 5 applies. If any of the other 4 special
functions is implemented manually, the destructor will not be
generated. As a quick reminder, the special functions besides the
destructor are:

• copy constructor
• copy assignment operator
• move constructor
• move assignment operator

A destructor is needed if the class acquires resources that have to


be released. Remember, you should write RAII class, meaning that
⁵⁸http://cpptruths.blogspot.com/2014/09/short-circuiting-overloaded-and-using.html
⁵⁹https://stackoverflow.com/questions/25913237/is-there-actually-a-reason-why-
overloaded-and-dont-short-circuit
Polymorphism, inheritance and virtual functions 60

resources are acquired on construction and released on destruction.


This can be things like releasing connections, closing file handles,
saving transactions, etc.
As said a destructor has no parameter, it cannot be const, volatile
or static, and there can be only one destructor. Hence it cannot be
overloaded.
On the other hand, a destructor can be virtual, but that’s the topic
for tomorrow.
References:

• C++ Reference: Destructor⁶⁰


• C++ Reference: The rule of three/five/zero⁶¹

Question 28: What is the output of


the following piece of code and why?
1 #include <iostream>
2
3 class A {
4 public:
5 int value() { return 1; }
6 int value() const { return 2; }
7 };
8
9 int main() {
10 A a;
11 const auto b = a.value();
12 std::cout << b << '\n';
13 }
⁶⁰https://en.cppreference.com/w/cpp/language/destructor
⁶¹https://en.cppreference.com/w/cpp/language/rule_of_three
Polymorphism, inheritance and virtual functions 61

The output is going to be 1. If a would have been declared as const


and b not, the result would be 2. In fact, the result doesn’t depend
on b’s constness at all.
Why is that?
If a function has two overloaded versions where one is const and
the other is not, the compiler will choose which one to call based
on whether the object is const or not.
On the other hand, it has nothing to do with the constness of the
variable returned.
This was just a simple introduction to the coming days when we
are going to talk about constness, const correctness and how to use
the const keyword.
Reference:

• C++ Core Guidelines: What’s the deal with “const-


overloading”⁶²

Question 29: How to use the = delete


specifier in C++?
The question could also be how to disallow implicit conversions for
function calls?
You have a function taking integer numbers. Whole numbers. Let’s
say it takes as a parameter how many people can sit in a car. It
might be 2, there are some strange three-seaters, for some luxury
cars it’s 4 and for the vast majority, it’s 5. It’s not 4.9. It’s not 5.1 or
not even 5 and a half. It’s 5. We don’t traffic body parts.
How can you enforce that you only receive whole numbers as a
parameter?
⁶²https://isocpp.org/wiki/faq/const-correctness#const-overloading
Polymorphism, inheritance and virtual functions 62

Obviously, you’ll take an integer parameter. It might be int, even


unsigned or simply a short. There are a lot of options. You probably
even document that the numberOfSeats parameter should be an
integral number.
Great!
So what happens if the client call still passes a float?

1 #include <iostream>
2
3 void foo(int numberOfSeats) {
4 std::cout << "Number of seats: " << numberOfSeats << '\\
5 n';
6 // ...
7 }
8
9 int main() {
10 foo(5.6f);
11 }
12 /*
13 Number of seats: 5
14 */

The floating-point parameter is accepted and narrowed down into


an integer. You cannot even say that it’s rounded, it’s implicitly
converted into an integer.
You might say that this is fine and in certain situations it probably
is. But in others, this behaviour is simply not acceptable.
What can you do in such cases to avoid this problem?
Obviously, you can handle it on the caller side, but

• if foo is often used, it’d be tedious to do checks


• if foo is part of an API used by the external world, it’s out of
your control
Polymorphism, inheritance and virtual functions 63

Since C++11, we can use the delete specifier in order to restrict


certain types for copying of moving, and in fact even from poly-
morphic usage.
But = delete can be used for more. It can be applied to any function,
should they be members or free.
If you don’t want to allow implicit conversions from floating-point
numbers, you can simply delete foo’s overloaded version with a
float:

1 #include <iostream>
2
3 void foo(int numberOfSeats) {
4 std::cout << "Number of seats: " << numberOfSeats << '\\
5 n';
6 // ...
7 }
8
9 void foo(double) = delete;
10 int main() {
11 foo(5.6f);
12 }
13 /*
14 main.cpp: In function 'int main()':
15 main.cpp:10:6: error: use of deleted function 'void foo(d\
16 ouble)'
17 10 | foo(5.6f);
18 | ~~~^~~~~~
19 main.cpp:8:6: note: declared here
20 8 | void foo(double) = delete;
21 | ^~~
22
23 */

That’s it. By deleting some overloads of a function, you can forbid


implicit conversions from certain types to the types you expect.
Polymorphism, inheritance and virtual functions 64

Now, you are in complete control of what kind of parameters your


users can pass to your API.
Reference:

• Sandor Dargo’s blog: Three ways to use the = delete specifier


in C++⁶³

⁶³https://www.sandordargo.com/blog/2021/01/06/three-ways-to-use-delete-specifier-cpp
Lambda functions
The next two questions will be about lambda functions, one of the
most important features of C++11. These questions assume that you
understand what lambda functions are. If that’s not the case, check
out this introduction⁶⁴.

Question 30: What are immediately


invoked lambda functions?
They are lambda functions that are invoked immediately one could
say. Ok, but what does it mean immediately?
It means, that the lambda is not even stored and denoted by a
variable, but where you create it, you invoke it right away.
This is not an immediately invoked lambda:

1 auto l = [](){return 42;}; // Here we created a simple la\


2 mbda
3
4 int fortyTwo = l(); // Here we invoke it

In the above case, you can reuse the lambda, you can pass it around,
you can invoke it as often as you want, as you need it.
On the other hand, in the following example, you immediately
invoke it, which implies that you don’t store the lambda itself. By
definition, IILFs cannot be stored. If they are stored, they are not
invoked immediately.
⁶⁴https://www.learncpp.com/cpp-tutorial/introduction-to-lambdas-anonymous-
functions/
Lambda functions 66

1 auto fortyTwo = [](){return 42;}(); // those parantheses \


2 at the end invoke the lambda expression!

Why is this concept powerful and worth mentioning?


It helps to perform complex initialization of variables. This is
important because a good way to help the C++ compiler is to
declare all variables not supposed to change as const.
In most cases, it’s very easy, you just put const next to the type and
initialize your variable right on the spot. But you might run into
situations where you have different possible values at your hands.
Let’s start with a simple example.

1 // Bad Idea
2 std::string someValue;
3
4 if (caseA) {
5 return std::string{"Value A"};
6 } else {
7 return std::string{"Value B"};
8 }

This is bad, because as such someValue is not const. Can we make


it const? Sure we can. We might use a ternary operator.

1 const std::string someValue = caseA ? std::string{"Value \


2 A"} : std::string{"Value B"};

Easy peasy.
But what to do if there are 3 different possibilities or even more?
You have different options and among those IIFLs is one.
Lambda functions 67

1 const std::string someValue = [caseA, caseB] () {


2 if (caseA) {
3 return std::string{"Value A"};
4 } else if (caseB) {
5 return std::string{"Value B"};
6 } else {
7 return std::string{"Value C"};
8 }
9 }();

This way, you can perform complex initializations of const vari-


ables, yet you don’t have to find a proper place nor a name for the
initialization logic.
Performance-wise we get all the performance gain of having a
const variable and we lose nothing compared to ternaries or helper
functions. You can find more details regarding the performance
analysis here⁶⁵.
References:

• Sandor Dargo’s Blog: Immediately Invoked Lambda Func-


tions⁶⁶
• My Least Favorite Anti-Pattern by Conor Hoekstra⁶⁷

Question 31: What kind of captures


are available for lambda
expressions?
First of all, a lambda expression looks like this:
⁶⁵https://www.sandordargo.com/blog/2020/02/19/immediately-invoked-lambda-functions
⁶⁶www.sandordargo.com/blog/2020/02/19/immediately-invoked-lambda-functions
⁶⁷https://www.youtube.com/watch?v=CjHgL5EQdcY
Lambda functions 68

capture⁶⁸ -> returnType

The capture is a comma-separated list of zero or more captures,


optionally beginning with the capture-default. The capture list
defines the outside variables that are accessible from within the
lambda function body.
The only capture defaults are:

• & (implicitly capture the used automatic variables by refer-


ence) and
• = (implicitly capture the used automatic variables by copy).

The current object (*this) can be implicitly captured if either of


the capture defaults is present. If implicitly captured, it is always
captured by reference, even if the capture default is =. However
since C++20 the implicit capture of *this with the capture default
= is deprecated.

The following types of captures are available:


- By-copy capture (since C++11)

1 int num;
2 auto l = [num](){}

• By-copy capture that is a pack expansion (since C++11)

⁶⁸parameters
Lambda functions 69

1 template <typename Args>


2 void f(Args... args) {
3 auto l = [args...] {
4 return g(args...);
5 };
6 l();
7 }

* by-reference capture (since C++11)

1 int num=42;
2 auto l = [&num](){};

* by-reference capture that is a pack expansion (since C++11)

1 template <typename Args>


2 void f(Args... args) {
3 auto l = [&args...] { return g(args...);};
4 l();
5 }

• by-reference capture of the current object (since C++11)

1 auto l = [this](){};

• by-copy capture with an initializer (since C++14)

1 auto l = [num=5](){};

• by-reference capture with an initializer (since C++14)


Lambda functions 70

1 int num=42;
2 auto l = [&num2=num](){};

• by-copy capture of the current object (since C++17)

1 auto l = [*this](){};

• by-copy capture with an initializer that is a pack expansion


(since C++20)

1 template <typename Args>


2 auto delay_invoke_foo(Args... args) {
3 return [...args=std::move(args)]() -> decltype(auto) {
4 return foo(args...);
5 };
6 }

• by-reference capture with an initializer that is a pack expan-


sion (since C++20)

1 template <typename Args>


2 auto delay_invoke_foo(Args... args) {
3 return [&...args=std::move(args)]() -> decltype(auto) {
4 return foo(args...);
5 };
6 }

References:

• C++ Reference: Lambda expressions⁶⁹


• Learn C++: Lambda captures⁷⁰

⁶⁹https://en.cppreference.com/w/cpp/language/lambda
⁷⁰https://www.learncpp.com/cpp-tutorial/lambda-captures/
How to use the const
qualifier in C++
In this chapter, during the next couple of questions, we’ll learn
about the const qualifier and its proper usage.

Question 32: What is the output of


the following piece of code and why?
1 #include <iostream>
2
3 class A {
4 public:
5 int value() { return 1; }
6 int value() const { return 2; }
7 };
8
9 int main() {
10 A a;
11 const auto b = a.value();
12 std::cout << b << '\n';
13 }

The output is going to be 1. If a would have been declared as const


and b not, the result would be 2. In fact, the result doesn’t depend
on b’s constness.
Why is that?
If a function has two overloaded versions where one is const and
How to use the const qualifier in C++ 72

the other is not, the compiler will choose which one to call based
on whether the object is const or not.
On the other hand, it has nothing to do with the constness of the
variable returned.
This was just a simple introduction to the coming days when we
are going to talk about constness and const correctness. That’s an
important topic in C++, as using the const keyword.
Reference:

• Tutorials Point: Function overloading and const keyword in


C++⁷¹

Question 33: What are the


advantages of using const local
variables?
By declaring a local variable const you mark it immutable. It should
never change its value. If you still try to modify it later on, you’ll
get a compilation error. For global variables, this is rather useful.
Otherwise, you have no idea who might modify their values. Of
course, we should avoid using global variables, do you remember
the static initialization order fiasco?
Moreover, declaring variables as const also helps the compiler to
perform some optimizations. Unless you explicitly mark a variable
const, the compiler will not know (at least not for sure) that the
given variable should not be changed. Again this is something that
we should use whenever it is possible.
In real life, I find that we tend to forget the value making variables
const, even though there are good examples at conference talks⁷²
⁷¹https://www.tutorialspoint.com/function-overloading-and-const-keyword-in-cplusplus
⁷²https://youtu.be/zBkNBP00wJE?t=1614
How to use the const qualifier in C++ 73

and it really has no bad effect on your code, on maintainability.


This is such an important idea that in Rust, all your variables are
declared as const unless you say they should be mutable.
We have no reason not to follow similar practices.
Declare your local variables const if you don’t plan to modify them.
Regarding global variables, well, avoid using them, but if you do,
also make them const whenever possible.
Reference:

• CppCon 2016: Jason Turner “Rich Code for Tiny Computers:


A Simple Commodore 64 Game in C++17”⁷³
• Sandor Dargo’s Blog: When to use const in C++? Part I:
functions and local variables⁷⁴

Question 34: Is it a good idea to have


const members in a class?

Why would you have const members in the first place?


Because you might want to signal that they are immutable, that
they should never change.
Unfortunately, there are some implications.
The first is that classes with const members are not assignable:

⁷³https://www.youtube.com/watch?v=zBkNBP00wJE&t=1614s
⁷⁴https://www.sandordargo.com/blog/2020/11/04/when-use-const-1-functions-local-
variables
How to use the const qualifier in C++ 74

1 class MyClassWithConstMember {
2 public:
3 MyClassWithConstMember(int a) : m_a(a) {}
4
5 private:
6 const int m_a;
7 };
8
9 int main() {
10 MyClassWithConstMember o1{666};
11 MyClassWithConstMember o2{42};
12 o1 = o2;
13 }
14 /*
15 main.cpp: In function 'int main()':
16 main.cpp:12:8: error: use of deleted function 'MyClassWit\
17 hConstMember& MyClassWithConstMember::operator=(const MyC\
18 lassWithConstMember&)'
19 12 | o1 = o2;
20 | ^~
21 main.cpp:1:7: note: 'MyClassWithConstMember& MyClassWithC\
22 onstMember::operator=(const MyClassWithConstMember&)' is \
23 implicitly deleted because the default definition would b\
24 e ill-formed:
25 1 | class MyClassWithConstMember {
26 | ^~~~~~~~~~~~~~~~~~~~~~
27 main.cpp:1:7: error: non-static const member 'const int M\
28 yClassWithConstMember::m_a', cannot use default assignmen\
29 t operator
30 */

If you think about it, it makes perfect sense. A variable is some-


thing you cannot change after initialization. And when you want
to assign a new value to an object, thus to its members, it’s not
possible anymore.
How to use the const qualifier in C++ 75

As such it also makes it impossible to use move semantics, for the


same reason.
From the error messages, you can see that the corresponding special
functions, such as the assignment operator or the move assignment
operator were deleted. Which means we have to implement them
by hand. Don’t forget about the rule of 5⁷⁵. If we implement one,
we have to implement all the 5.
Let’s take the assignment operator as an example. What should we
do with it?
Do we skip assigning to the const members? Not so great, either we
depend on that value somewhere, or we should not store the value.
If we really want to implement it, we must use const_cast as a
workaround. As you cannot cast the constness away from values,
you have to turn the member values into temporary non-const
pointers.

1 #include <utility>
2 #include <iostream>
3
4 class MyClassWithConstMember {
5 public:
6 MyClassWithConstMember(int a) : m_a(a) {}
7 MyClassWithConstMember& operator=(const MyClassWithCons\
8 tMember& other) {
9 int* tmp = const_cast<int*>(&m_a);
10 *tmp = other.m_a;
11 std::cout << "copy assignment\n";
12 return *this;
13 }
14
15 int getA() {return m_a;}
16
⁷⁵https://www.fluentcpp.com/2019/04/19/compiler-generated-functions-rule-of-three-
and-rule-of-five/
How to use the const qualifier in C++ 76

17 private:
18 const int m_a;
19 };
20
21 int main() {
22 MyClassWithConstMember o1{666};
23 MyClassWithConstMember o2{42};
24 std::cout << "o1.a: " << o1.getA() << std::endl;
25 std::cout << "o2.a: " << o2.getA() << std::endl;
26 o1 = o2;
27 std::cout << "o1.a: " << o1.getA() << std::endl;
28 }

Is this worth it?


You have your const member, fine. You have the assignment
working, fine. Then if anyone comes later and wants to do the same
“magic” outside of the special functions, for sure, it would be a red
flag in a code review.
Let’s not commit this so quickly.
Having this “magic” in the special functions is already a red flag!
const_cast might lead to undefined behaviour:

$5.2.11/7 - Note: Depending on the type of the object, a


write operation through the pointer, lvalue or pointer
to data member resulting from a const_cast that
casts away a const-qualifier may produce undefined
behaviour (7.1.5.1).

We’ve just had a look at the copy assignment and it wouldn’t work
without risking undefined behaviour.
It’s not worth it!
References:
How to use the const qualifier in C++ 77

• Fluent C++: Compiler-generated Functions, Rule of Three and


Rule of Five⁷⁶
• Sandor Dargo’s Blog: When to use const in C++? Part II:
member variables⁷⁷

Question 35: Does it make sense to


return const objects by value?
Most probably it won’t make so much sense.
Why is that?
Putting const somewhere shows the reader (and the compiler of
course) that something should not be modified. When we return
something by value it means that a copy will be made for the caller.
Okay, you might have heard about copy elision⁷⁸ and its special
form, return value optimization (RVO), but essentially we are still
on the same page. The caller gets his own copy.
Does it make sense to make that own copy const?
Imagine that you buy a house but you cannot modify it? While
there can be special cases, in general, you want your house to be
your castle. Similarly, you want your copy to really be your object
and you want to be able to do with it just whatever as an owner of
it.
It doesn’t make sense and it’s misleading to return by value a const
object.
Not just misleading, but probably even hurting you.
Even hurting? How can it be?
Let’s say you have this code:
⁷⁶https://www.fluentcpp.com/2019/04/19/compiler-generated-functions-rule-of-three-
and-rule-of-five/
⁷⁷https://www.sandordargo.com/blog/2020/11/11/when-use-const-2-member-variables
⁷⁸https://en.cppreference.com/w/cpp/language/copy_elision
How to use the const qualifier in C++ 78

1 class SgWithMove {
2 // ...
3 };
4
5 SgWithMove foo() {
6 // ...
7 }
8
9 int main() {
10 SgWithMove o;
11 o = foo();
12 }

By using a debugger or by adding some logging to your special


functions, you can see that RVO was perfectly applied and there
was a move operation taking place when foo()s return value was
assigned to o.
Now let’s add that infamous const to the return type.

1 class SgWithMove {
2 // ...
3 };
4
5 const SgWithMove bar() {
6 // ...
7 }
8
9 int main() {
10 SgWithMove o;
11 o = bar();
12 }

Following up with the debugger we can see that we didn’t benefit


from a move, but actually, a copy was made.
How to use the const qualifier in C++ 79

We are returning a const SgWithMove and that is something we


cannot pass as SgWithMove&&. It would discard the const qualifier.
(A move would alter the object being moved) Instead, the copy
assignment (const SgWithMove&) is called and we just made another
copy.
Please note that there are important books advocating for returning
user-defined types by const value. They were right in their own age,
but since then C++ went through a lot of changes and this piece of
advice became obsolete.
References:

• C++ Reference: Copy elision⁷⁹


• C++ Reference: When to use const in C++? Part III: return
types⁸⁰

Question 36: How should you return


const pointers from a function?

Pointers are similar to references in the sense that the pointed object
must be alive at least as long as the caller wants to use it. You
can return the address of a member variable if you know that the
object will not get destroyed as long as the caller wants the returned
address. What is important to emphasize once again is that we can
never return a pointer to a locally initialized variable.
But even that is not so self-evident. Let’s step back a little bit.
What do we return when we return a pointer?
We return a memory address. The address can be of anything.
Technically it can be a random place, it can be a null pointer or
it can be the address of an object. (OK, a random place can be the
⁷⁹https://en.cppreference.com/w/cpp/language/copy_elision
⁸⁰https://www.sandordargo.com/blog/2020/11/18/when-use-const-3-return-types
How to use the const qualifier in C++ 80

address of a valid object, but it can be simply garbage. After all, it’s
random.)
Even if we talk about an object that was declared in the scope of
the enclosing function, that object could have been declared either
on the stack or on the heap.
If it was declared on the stack (no new), it means that it will be
automatically destroyed when we leave the enclosing function.
If the object was created on the heap (with new), that’s not a problem
anymore, the object will be alive, but you have to manage its
lifetime. Except if you return a smart pointer, but that’s beyond
the scope of this article.
So we have to make sure that we don’t return a dangling pointer,
but after that, does it make sense to return a const pointer?

int * const func () const

The function is constant, and the returned pointer is constant but


the data we point at can be modified. However, I see no point in
returning a const pointer because the ultimate function call will be
an rvalue, and rvalues of non-class type cannot be const, meaning
that const will be ignored anyway.

const int* func () const

This is a useful thing. The pointed data cannot be modified.

const int * const func() const

Semantically this is almost the same as the previous option. The


data we point at cannot be modified. On the other hand, the
constness of the pointer itself will be ignored.
How to use the const qualifier in C++ 81

So does it make sense to return a const pointer? It depends on what


is const. If the constness refers to the pointed object, yes it does. If
you try to make the pointer itself const, it doesn’t make sense as it
will be ignored.
Reference:

• Sandor Dargo’s Blog: When to use const in C++? Part III:


return types⁸¹

Question 37: Should functions


return const references?
It depends, you might face an issue. Maybe you’ll have a dangling
reference⁸². The problem with returning const references is that the
returned object has to outlive the caller. Or at least it has to live as
long.

1 void f() {
2 MyObject o;
3 const auto& aRef = o.getSomethingConstRef();
4 aRef.doSomething();
5 }

Will that call work? It depends. If MyObject::getSomethingConstRef()


returns a const reference of a local variable it will not work. It is
because that local variable gets destroyed immediately once we
get out of the scope of the function.

⁸¹https://www.sandordargo.com/blog/2020/11/18/when-use-const-3-return-types#return-
const-pointers
⁸²https://en.wikipedia.org/wiki/Dangling_pointer
How to use the const qualifier in C++ 82

1 const T& MyObject::getSomethingConstRef() {


2 T ret;
3 // ...
4 return ret; // ret gets destroyed right after, the retu\
5 rned reference points at its ashes
6 }

This is what is called a dangling reference.


On the other hand, if we return a reference to a member of
MyObject, there is no problem in our above example.

1 class MyObject {
2 public:
3 // ...
4
5 const T& getSomethingConstRef() {
6 // ...
7 return m_t; // m_t lives as long as our MyObject inst\
8 ance is alive
9 }
10 private:
11 T m_t;
12 };

It’s worth noting that outside of f() we wouldn’t be able to use


aRef as the instance of MyObject gets destroyed at the end of the
function f().
So shall we return const references?
As so often the answer is it depends. Definitely not automatically
and by habit. We should return constant references only when are
sure that the referenced object will be still available by the time we
want to reference it.
At the same time:
How to use the const qualifier in C++ 83

Never return locally initialized variables by reference!


References:

• Wikipedia: Dangling Reference⁸³


• Sandor Dargo’s Blog: When to use const in C++? Part III:
return types⁸⁴

Question 38: Should you take plain


old data types by const reference as a
function parameter?
They should not be passed as const references or pointers. It’s
inefficient. These data types can be accessed with one memory
read if passed by value. On the other hand, if you pass them by
reference/pointer, first the address of the variable will be read and
then by dereferencing it, the value. That’s 2 memory reads instead
of one.
We shall not take fundamental data types by const&.
But should we take them simply by const?
As always, it depends.
If we don’t plan to modify their value, yes we should. For better
readability, for the compiler and for the future.

1 void setFoo(const int foo) {


2 this->m_foo = foo;
3 }
⁸³https://en.wikipedia.org/wiki/Dangling_pointer
⁸⁴https://www.sandordargo.com/blog/2020/11/18/when-use-const-3-return-types
How to use the const qualifier in C++ 84

I know this seems like overkill, but it doesn’t hurt, it’s explicit and
you don’t know how the method would grow in the future. Maybe
there will be some additional checks done, exception handling, and
so on.
And if it’s not marked as const, maybe someone will accidentally
change its value and cause some subtle errors.
If you mark foo const, you make this scenario impossible.
What’s the worst thing that can happen? You’ll actually have to
remove the const qualifier, but you’ll do that intentionally.
On the other hand, if you have to modify the parameter, don’t mark
it as const.
From time to time, you can see the following pattern:

1 void doSomething(const int foo) {


2 // ...
3 int foo2 = foo;
4 foo2++;
5 // ...
6 }

Don’t do this. There is no reason to take a const value if you plan


to modify it. One more variable on the stack in vain, one more
assignment without any reason. Simply take it by value.

1 void doSomething(int foo) {


2 // ...
3 foo++;
4 // ...
5 }

So we don’t take fundamental data types by const& and we only


mark them const when we don’t want to modify them.
Reference:
How to use the const qualifier in C++ 85

• Sandor Dargo’s Blog: When to use const in C++? Part IV:


parameters⁸⁵

Question 39: Should you pass


objects by const reference as a
function parameter?
If we’d take a class by value as a parameter, it’d mean that we
would make a copy of them. In general, copying an object is more
expensive than just passing a reference around.
So the rule of thumb to follow is not to take an object by value, but
by const& to avoid the copy.
Obviously, if you want to modify the original object, then you only
take it by reference and omit the const.
You might take an object by value if you know you’d have to make
a copy of it.

1 void doSomething(const ClassA& foo) {


2 // ...
3 ClassA foo2 = foo;
4 foo2.modify();
5 // ...
6 }

In that case, just simply take it by value. We can spare the cost of
passing around a reference and the mental cost of declaring another
variable and calling the copy constructor.
Although it’s worth noting that if you are accustomed to taking ob-
jects by const& you might have done some extra thinking whether
passing by value was on purpose or by mistake.
⁸⁵https://www.sandordargo.com/blog/2020/11/25/when-use-const-4-parameters
How to use the const qualifier in C++ 86

So the balance of extra mental efforts is questionable.

1 void doSomething(ClassA foo) {


2 // ...
3 foo.modify();
4 // ...
5 }

You should also note that there are objects where making the copy
is less expensive or similar to the cost of passing a reference. It’s the
case for Small String Optimization⁸⁶ or for std::string_view. This
is beyond the scope of today’s lesson.
For objects, we can say that by default we should take them by
const reference and if we plan to locally modify them, then we
can consider taking them by value. But never by const value, which
would force a copy but not let us modify the object.
Reference:

• Sandor Dargo’s Blog: When to use const in C++? Part IV:


parameters⁸⁷

Question 40: Does the signature of a


function declaration has to match
the signature of the function
definition?
No, it does not. At least not always. The const qualifier for
parameters is not always relevant for function definitions, but it
is for function declarations.
For example, the following code is valid:
⁸⁶https://blogs.msmvps.com/gdicanio/2016/11/17/the-small-string-optimization/
⁸⁷https://www.sandordargo.com/blog/2020/11/25/when-use-const-4-parameters
How to use the const qualifier in C++ 87

1 #include <iostream>
2
3 class MyClass {
4 public:
5 void f(const int);
6 };
7
8 void MyClass::f(int a) {
9 a = 42;
10 std::cout << a << std::endl;
11 }
12
13 int main() {
14 int a=5;
15 MyClass c;
16 c.f(a);
17 }

The const in the function declaration would imply that it won’t


modify what you pass in. But it can. At the same time, it’s not a big
deal, as it will only modify its own copy, not the original variable.
Please note that as soon as we turn the parameter into a reference
in both the declaration and the definition, the code stops compiling.

1 #include <iostream>
2
3 class MyClass {
4 public:
5 void f(const int&);
6 };
7
8 void MyClass::f(int& a) {
9 a = 42;
10 std::cout << a << std::endl;
11 }
How to use the const qualifier in C++ 88

12
13 int main() {
14 int a=5;
15 MyClass c;
16 c.f(a);
17 std::cout << a << std::endl;
18 }
19 /*
20 main.cpp:8:6: error: no declaration matches 'void MyClass\
21 ::f(int&)'
22 8 | void MyClass::f(int& a) {
23 | ^~~~~~~
24 main.cpp:5:8: note: candidate is: 'void MyClass::f(const \
25 int&)'
26 5 | void f(const int&);
27 | ^
28 main.cpp:3:7: note: 'class MyClass' defined here
29 3 | class MyClass {
30 | ^~~~~~~
31 */

But even for our original use-case, clang-tidy warns:

Parameter 1 is const-qualified in the function


declaration; const-qualification of parameters only has
an effect in function definitions

Reference:

• How to use const in C++ by Sandor Dargo⁸⁸


⁸⁸https://leanpub.com/cppconst/
How to use the const qualifier in C++ 89

Question 41: Explain what consteval


and constinit bring to C++?
C++11 introduced constexpr expressions that might be evaluated
at compile-time.
C++20 introduces two new related keywords, consteval and
constinit.

consteval can be used with functions:

1 consteval int sqr(int n) {


2 return n * n;
3 }

consteval functions are guaranteed to be executed at compile-


time, thus they create compile-time constants. They cannot allocate
or deallocate data, nor they can interact with static or thread-local
variables.
constinit can be applied to variables with static storage duration
or thread storage duration. So a local or member variable cannot
be constinit. What it guarantees is that the initialization of the
variable happens at compile-time.
It must be noted that while constexpr and const variables cannot
be changed once they are assigned, a constinit variable is not
constant, its value can change.
I hope you enjoyed our journey around constness.
References:

• CppReference: consteval specifier⁸⁹


• CppReference: constinit specifier⁹⁰
⁸⁹https://en.cppreference.com/w/cpp/language/consteval
⁹⁰https://en.cppreference.com/w/cpp/language/constinit
How to use the const qualifier in C++ 90

• Modernes C++: Two new Keywords in C++20: consteval and


constinit⁹¹

⁹¹https://www.modernescpp.com/index.php/c-20-consteval-and-constinit
Some best practices in
modern C++
Now let’s switch and discuss some miscellaneous practices that
modern C++ brought for us.

Question 42: What is aggregate


initialization?
Aggregate initialization or brace-initialization (sometimes written
as {}-initialization) was added to the language in C++11.
So what is an aggregate?
It’s either an array type or a class type with some restrictions that
you can read here⁹².
In its simplest form, it looks exactly like normal constructor calls
with parentheses, but this time with braces.

1 std::string text{"abcefg"};
2
3 Point a{5,4,3}; //Point is class taking 3 integers as par\
4 ameters

You can also use it with auto:

⁹²https://en.cppreference.com/w/cpp/language/aggregate_initialization
Some best practices in modern C++ 92

1 auto text = std::string{"abcefg"};


2
3 auto a = Point{5,4,3}; // Point is class taking 3 integer\
4 s as parameters\n

Aggregate initialization can be also used for initliazing containers:

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


2
3 auto otherNumbers = std::vector<int>{6, 7, 8, 9, 10};

This is much better than the previous ways of initialization, where


after creating the container we had to add the elements one by one.
With aggregate initialization, we can easily create const containers
if we know that they won’t change anymore. That’s something that
was not possible before C++11.

1 // Before C++11
2
3 std::vector<int> myNums;
4 myNums.push_back(3);
5 myNums.push_back(42);
6 myNums.push_back(51);
7
8 // From C++11
9 const std::vector<int> myConstNums{3, 42, 51};

But this is not everything. {}-initialization has two advantages.


It’s not prone to most vexing parse
According to the C++ standard whatever can be interpreted as a
declaration, it will be interpreted as one.
As such MyClass o(); is not a command to create an instance
of MyClass with the name o calling the default constructor of
Some best practices in modern C++ 93

MyClass.Instead, it’s a declaration of a function called o that takes


no parameters but returns a MyClass type.
If one uses braces, it won’t be interpreted as a declaration, but rather
a variable declaration as in 99% of the cases the developer intended.
It doesn’t allow narrowing conversions
Narrowing or more precisely narrowing conversion is an implicit
conversion of arithmetic values including a loss of accuracy. De-
pending on your circumstances, that can be extremely dangerous.
The following examples show the problem with the classical initial-
ization for numerical types. It doesn’t matter whether we use direct
initialization or assignment.

1 #include <iostream>
2
3 int main() {
4 int i1(3.14);
5 int i2=3.14;
6 std::cout << "i1: " << i1 << std::endl;
7 // i1: 3
8 std::cout << "i2: " << i2 << std::endl;
9 // i2: 3
10 int i3{3.14} // error: narrowing conversion of '3.14000\
11 000000000
12 unsigned int i4{-41}; // error: narrowing conversion of\
13 '-41'
14 }

As we can see, with brace initialization the compiler informs us


about the narrowing in front of a nice error.
So the three advantages of {}-initialization are

• Direct initialization of containers


• No more most vexing parse
Some best practices in modern C++ 94

• Detection of implicit narrowing conversions

Reference:

• C++ Reference: Aggregate initialization⁹³

Question 43: What are explicit


constructors and what are their
advantages?
The explicit specifier specifies that a constructor cannot be used
for implicit conversions.
If not used, the compiler is allowed to make one implicit conversion
to resolve the parameters to a function. The compiler can use
constructors callable with a single parameter to convert from one
type to another in order to get the right type for a parameter.
Let’s take this example:

1 class WrappedInt {
2 public:
3 // single parameter constructor, can be used as an impl\
4 icit conversion
5 WrappedInt(int number) : m_number (number) {}
6
7 // using explicit, implicit conversions are not allowed
8 // explicit WrappedInt(int number) : m_number (number) \
9 {}
10
11 int GetNumber() { return m_number; }
12 private:
⁹³https://en.cppreference.com/w/cpp/language/aggregate_initialization
Some best practices in modern C++ 95

13 int m_number;
14 };
15
16 void doSomething(WrappedInt aWrappedInt) {
17 int i = aWrappedInt.GetNumber();
18 }
19
20 int main() {
21 doSomething(42);
22 }

The argument is not a WrappedInt object, just a plain old int.


However, there exists a constructor for WrappedInt that takes an
int so this constructor can be used to convert the parameter to the
correct type.
The compiler is allowed to do this once for each parameter.
Prefixing the explicit keyword to the constructor prevents the com-
piler from using that constructor for implicit conversions. Adding
it to the above class will create a compiler error at the function
call doSomething(42). It is now necessary to call for conversion
explicitly with doSomething(WrappedInt(42)).
The reason you might want to do this is to avoid accidental
construction that can hide bugs. Let’s think about strings. You
have a MySpecialString class with a constructor taking an int.
This constructor creates a string with the size of the passed in int.
You have a function print(const MySpecialString&), and you call
print(3) (when you actually intended to call print(“3”)). You expect
it to print “3”, but it prints an empty string of length 3 instead.
A third example, is also about strings, let’s say this is
MySpecialString:
Some best practices in modern C++ 96

1 class MySpecialString {
2 public:
3 MySpecialString(int n); // allocate n bytes to the MySp\
4 ecialString object
5 MySpecialString(const char *p); // initializes object w\
6 ith char *p
7 };

What happens with this constructor call?

1 MySpecialString mystring = 'x';

The character x will be implicitly converted to int and then the


MySpecialString(int) constructor will be called. But, this is not what
the user might have intended. So, to prevent such conditions, we
shall define the constructor as explicit:

1 class MySpecialString {
2 public:
3 explicit MySpecialString (int n); //allocate n bytes
4 MySpecialString(const char *p); // initializes object w\
5 ith string p
6 };

To avoid such subtle bugs, one should always consider making


single argument constructors explicit initially (more or less au-
tomatically), and removing the explicit keyword only when the
implicit conversion is wanted by design.
Reference:

• C++ Reference: explicit specifier⁹⁴


⁹⁴https://en.cppreference.com/w/cpp/language/explicit
Some best practices in modern C++ 97

Question 44: What are user-defined


literals?
User-defined literals allow integer, floating-point, character, and
string literals to produce objects of user-defined type by defining a
user-defined suffix.
User-defined literals can be used with integer, floating-point, char-
acter and string types.
They can be used for conversions, you can write a degrees-to-radian
converter:

1 constexpr long double operator"" _deg ( long double deg )\


2 {
3 return deg * 3.14159265358979323846264L / 180;
4 }
5 //...
6 std::cout << "50 degrees as radians: " << 50.0_deg << std\
7 ::endl;
8
9 // 50 degrees as radians: 0.872665

But what is probably more interesting is how it can help readability


and safety when you use strong types.
Let’s take this example from an earlier lesson:

1 auto myCar{Car{Horsepower{98u}}, DoorsNumber{4u},


2 Transmission::Automatic, Fuel::Gasoline};

It’s not very probable that you’d mix up the number of doors with
the power of the engine, but there can be other systems to measure
performance or other numbers taken by the Car constructor.
Some best practices in modern C++ 98

A very readable and safe way to initialize Car objects with hard-
coded values (so far example in unit tests) is via user-defined
literals:

1 //...
2 Horsepower operator"" _hp(int performance) {
3 return Horsepower{performance};
4 }
5
6 DoorsNumber operator"" _doors(int numberOfDoors) {
7 return DoorsNumber{numberOfDoors};
8 }
9 //...
10 auto myCar{Car{98_hp, 4_doors,
11 Transmission::Automatic, Fuel::Gasoline};

Of course, the number of ways to use user-defined literals is limited


only by your imagination, but numerical conversions, helping
strong typing are definitely two of the most interesting ones.
References:

• C++ Reference: User-defined literals⁹⁵


• Modernes C++: User-Defined Literals⁹⁶

Question 45: Why should we use


nullptr instead of NULL or 0?

The literal 0 is an int, not a pointer. If the compiler is looking at


a 0 where only a pointer can be used, it will interpret 0 as a null
pointer, but that’s only an implicit conversion, a second option if
⁹⁵https://en.cppreference.com/w/cpp/language/user_literal
⁹⁶https://www.modernescpp.com/index.php/user-defined-literals
Some best practices in modern C++ 99

you prefer. The same is true for NULL, though implementations are
allowed to give NULL an integral type other than int, such as long,
which is uncommon.
This has the following implication. In case you have a function with
three overloads, including integral types and pointers, you might
get some surprises:

1 #include <iostream>
2
3 void foo(int) {
4 std::cout << "foo(int) is called" << std::endl;
5 }
6
7 void foo(bool) {
8 std::cout << "foo(bool) is called" << std::endl;
9 }
10
11 void foo(void*) {
12 std::cout << "foo(void*) is called" << std::endl;
13 }
14
15 int main() {
16 foo(0); // calls foo(int), not foo(void*)
17 foo(NULL); // might not compile, but
18 // typically calls foo(int), never foo(void*)
19 }

Hence was created the guideline to avoid overloading on the pointer


and integral types.
On the other hand, nullptr doesn’t have an integral type. It’s type
is std::nullptr_t. It is a distinct type that is not itself a pointer
type or a pointer to member type. At the same time, it implicitly
converts to all raw pointer types, and that’s what makes nullptr
act as if it were a pointer of all types.
Some best practices in modern C++ 100

As such, with nullptr the pointer overload can be called:

1 foo(nullptr); // calls foo(void*) overload

For further advantages related to nullptr, we’d have to experiment


with template metaprogramming. For further details, I urge you to
check Item 8 or Effective Modern C++.
References:

• C++ Reference⁹⁷
• Effective Modern C++ by Scott Meyers⁹⁸

Question 46: What advantages does


alias have over typedef?

First, let’s see what is a typedef. In case, you want to avoid writing
all the time complex typenames, you can introduce a “shortcut”, a
typedef:

typedef std::unique_ptr<std::unordered_map<std::string,
std::string>> MyStringMap;

Since C++11, you can use alias declarations instead:


using MyStringMap = std::unique_ptr<std::unordered_-
map<std::string, std::string>>

They are doing exactly the same thing, but alias declarations offer
a couple of advantages.
In case of function pointers, they are more readable:

⁹⁷https://en.cppreference.com/w/cpp/types/nullptr_t
⁹⁸https://amzn.to/38gK5bd
Some best practices in modern C++ 101

1 typedef void (*MyFunctionPointer)(int, int);


2 using MyFunctionPointerAlias = void(*)(int, int);

Another advantage is that typedefs don’t support templatization,


but alias declarations do.
template<typename T> using MyAllocList = std::list<T,
MyAlloc<T>>;

With typedefs it’s much more obscure, and in some cases (check
here⁹⁹) you even have to use the “::type” suffix and in templates,
the “typename” prefix is often required to refer to typedefs.
C++14 even offers alias templates for all the C++11 type traits
transformations, such as std::remove_const_t<T>, std::remove_-
reference_t<T> or std::add_lvalue_reference_t<T>.

References:

• C++ Reference - Type Alias¹⁰⁰


• C++ Reference - Typedef specifier¹⁰¹
• Effective Modern C++ by Scott Meyers¹⁰²

Question 47: What are the


advantages of scoped enums over
unscoped enums?
C++98-style enums are now known as unscoped enums. You’d
declare them as such:

⁹⁹https://en.cppreference.com/w/cpp/language/type_alias
¹⁰⁰https://en.cppreference.com/w/cpp/language/type_alias
¹⁰¹https://en.cppreference.com/w/cpp/language/typedef
¹⁰²https://amzn.to/38gK5bd
Some best practices in modern C++ 102

1 enum Transmission { manual, automatic };


2
3 // compilation error, as manual is already declared
4 // as a different kind of entity
5 auto manual = false;

The modern scoped enums use the class keyword and the enumer-
ators are visible only within the enum. They convert to other types
only with a cast. They are usually called either scoped enums or
enum classes.

1 enum class Transmission { manual, automatic };


2
3 // now compiles, manual has not been declared
4 auto manual = false;
5
6 // error, no enumerator
7 // manual without the enum scope is just a bool
8 Transmission t = manual;
9
10 Transmission t = Transmission::manual; // OK
11 auto t = Transmission::manual; // OK

Both scoped and unscoped enums support the specification of the


underlying type. The default underlying type for scoped enums is
int. Unscoped enums have no default underlying type.

The specification happens with the same syntax:

1 enum Status: std::uint32_t { /*..*/ };


2 enum class Status: std::uint32_t { /*...*/ };

Unscoped enums may be forward-declared only if their declaration


specifies an underlying type, otherwise, the size of an enum is
unknown. On the other hand, as scoped enums by default have an
underlying type, they can be always forward declared.
Some best practices in modern C++ 103

References:

• C++ Reference¹⁰³
• Effective Modern C++ by Scott Meyers¹⁰⁴

Question 48: Should you explicitly


delete unused/unsupported special
functions or declare them as
private?
The first question to answer is why would you do any of the
options? You might not want a class to be copied or moved, so you
want to keep related special functions unreachable for the caller.
One option is to declare them as private or protected and the other
is that you explicitly delete them.

1 class NonCopyable {
2 public:
3 NonCopyable() {/*...*/}
4
5 // ...
6
7 private:
8 NonCopyable(const NonCopyable&); //not defined
9 NonCopyable& operator=(const NonCopyable&); //not defin\
10 ed
11 };

Before C++11 there was no other option than declaring the un-
needed special functions private and not implementing them. As
¹⁰³https://en.cppreference.com/w/cpp/language/enum
¹⁰⁴https://amzn.to/38gK5bd
Some best practices in modern C++ 104

such one could disallow copying objects (there was no move seman-
tics available back in time). The lack of implementation/definition
helps against accidental usages in members, friends, or when you
disregard the access specifiers. You’ll face a problem at linking time.
Since C++11 you can simply mark them deleted by declaring them
as = delete;

1 class NonCopyable {
2 public:
3 NonCopyable() {/*...*/}
4
5 NonCopyable(const NonCopyable&) = delete;
6 NonCopyable& operator=(const NonCopyable&) = delete;
7
8 // ...
9 private:
10 // ...
11 };

The C++11 way is a better approach because

• it’s more explicit than having the functions in the private


section which might only be a mistake
• in case you try to make a copy, you’ll already get an error at
compilation time

Deleted functions should be declared as public, not private. It’s not a


mandate by the compiler, but some compilers might only complain
that you call a private function, not that it’s deleted.
References:

• C++ Reference¹⁰⁵
• Effective Modern C++ by Scott Meyers¹⁰⁶
¹⁰⁵https://en.cppreference.com/w/cpp/language/function#Deleted_functions
¹⁰⁶https://amzn.to/38gK5bd
Some best practices in modern C++ 105

Question 49: How to use the = delete


specifier in C++?
The question could also be how to disallow implicit conversions for
function calls?
You have a function taking integer numbers. Whole numbers. Let’s
say it takes as a parameter how many people can sit in a car. It
might be 2, there are some strange three-seaters, for some luxury
cars it’s 4 and for the vast majority, it’s 5. It’s not 4.9. It’s not 5.1 or
not even 5 and a half. It’s 5. We don’t traffic body parts.
How can you enforce that you only receive whole numbers as a
parameter?
Obviously, you’ll take an integer parameter. It might be int, even
unsigned or simply a short. There are a lot of options. You probably
even document that the numberOfSeats parameter should be an
integral number.
Great!
So what happens if the client call still passes a float?

1 #include <iostream>
2
3 void foo(int numberOfSeats) {
4 std::cout << "Number of seats: " << numberOfSeats << '\\
5 n';
6 // ...
7 }
8
9 int main() {
10 foo(5.6f);
11 }
12 /*
Some best practices in modern C++ 106

13 Number of seats: 5
14 */

The floating-point parameter is accepted and narrowed down into


an integer. You cannot even say that it’s rounded, it’s implicitly
converted into an integer.
You might say that this is fine and in certain situations it probably
is. But in others, this behaviour is simply not acceptable.
What can you do in such cases to avoid this problem?
Obviously, you can handle it on the caller side, but

• if foo is often used, it’d be tedious to do checks


• if foo is part of an API used by the external world, it’s out of
your control

Since C++11, we can use the delete specifier in order to restrict


certain types for copying of moving, and in fact, even from poly-
morphic usage.
But = delete can be used for more. It can be applied to any function,
should they be members or free.
If you don’t want to allow implicit conversions from floating-point
numbers, you can simply delete foo’s overloaded version with a
float:
Some best practices in modern C++ 107

1 #include <iostream>
2
3 void foo(int numberOfSeats) {
4 std::cout << "Number of seats: " << numberOfSeats << '\\
5 n';
6 // ...
7 }
8
9 void foo(double) = delete;
10
11 int main() {
12 foo(5.6f);
13 }
14
15 /*
16 main.cpp: In function 'int main()':
17 main.cpp:11:6: error: use of deleted function 'void foo(d\
18 ouble)'
19 11 | foo(5.6f);
20 | ~~~^~~~~~
21 main.cpp:8:6: note: declared here
22 8 | void foo(double) = delete;
23 | ^~~
24 */

That’s it. By deleting some overloads of a function, you can forbid


implicit conversions from certain types to the types you expect.
Now, you are in complete control of what kind of parameters your
users can pass to your API.
Reference:

• Three ways to use the = delete specifier in C++¹⁰⁷


¹⁰⁷https://www.sandordargo.com/blog/2021/01/06/three-ways-to-use-delete-specifier-cpp
Some best practices in modern C++ 108

Question 50: What is a trivial class in


C++?
When a class or struct in C++ has only compiler-provided or
explicitly defaulted special member functions, then it is a trivial
type. It occupies a contiguous memory area. It can have members
with different access specifiers. In C++, the compiler is free to
choose how to order members in this situation. Therefore, you can
memcpy such objects but you cannot reliably consume them from a
C program. A trivial type T can be copied into an array of char or
unsigned char and safely copied back into a T variable. Note that
because of alignment requirements, there might be padding bytes
between type members.
Trivial types have a trivial a default constructor, trivial copy and
move constructors, trivial copy and move assignment operators and
a trivial destructor. In each case, trivial means the constructor/op-
erator/destructor is not user-provided and belongs to a class that
has

• no virtual functions or virtual base classes,


• no base classes with a corresponding non-trivial constructor/-
operator/destructor
• no data members of class type with a corresponding non-
trivial constructor/operator/destructor

Whether a class is trivial or not, you can verify with the std::is_-
trivial trait class. It checks whether the class is trivially copyable
(std::is_trivially_copyable) and is trivially default constructible
std::is_trivially_default_constructible.

Some examples:
Some best practices in modern C++ 109

1 #include <iostream>
2 #include <type_traits>
3
4 class A {
5 public:
6 int m;
7 };
8
9 class B {
10 public:
11 B() {}
12 };
13
14 class C {
15 public:
16 C() = default;
17 };
18
19 class D : C {};
20
21 class E { virtual void foo() {} };
22
23 int main() {
24 std::cout << std::boolalpha;
25 std::cout << std::is_trivial<A>::value << '\n';
26 std::cout << std::is_trivial<B>::value << '\n';
27 std::cout << std::is_trivial<C>::value << '\n';
28 std::cout << std::is_trivial<D>::value << '\n';
29 std::cout << std::is_trivial<E>::value << '\n';
30 }
31 /*
32 true
33 false
34 true
35 true
Some best practices in modern C++ 110

36 false
37 */

References:

• C++ Reference¹⁰⁸
• Microsoft Docs¹⁰⁹

¹⁰⁸https://en.cppreference.com/w/cpp/types/is_trivial
¹⁰⁹https://docs.microsoft.com/en-us/cpp/cpp/trivial-standard-layout-and-pod-
types?view=msvc-160
Smart pointers
During the the next couple of questions, we’ll focus on what smart
pointers are, when and why should we use them.
But first, let’s discuss a strongly related idiom.

Question 51: Explain the Resource


acquisition is initialization (RAII)
idiom
RAII is the bread and butter idiom of C++. It says that anything that
exists in a system only in limited supply must be acquired before
we start using it.
By such resources, we mean things like

• allocated heap memory,


• execution thread,
• open sockets,
• files,
• locked mutex,
• disk space,
• or database connections.

On the other hand, resources that are not acquired before using
them, are not part of RAII, such as

• CPU cores and time,


Smart pointers 112

- cache capacity,

• network bandwidth,
• electric power consumption
• or even stack memory.

But RAII is not just about acquiring resources, but also about
releasing them. RAII also guarantees that all resources are released
when the lifetime of their controlling object ends, in reverse order
of acquisition. Similarly, when the resource acquisition of an object
fails, all the resources that already have been successfully acquired
by the object or by any of its members must be released in reverse
order.
This is probably already enough to show what a bad name this
idiom has, something the language creator Bjarne Stroustrup also
regrets, a better name would probably be Scope Bound Resource
Management, but most people still refer to it as RAII.
In practical terms, an RAII class acquires all the resources
upon construction and releases everything on destruction time.
You shouldn’t have to call methods such as init()/open()
ordestroy/close.
In the standard library std::string, std::vector or std::thread
are such RAII classes.
On the other hand, if you consider raw pointers, they don’t share
the RAII concept. When a pointer goes out of scope, it doesn’t get
destroyed automatically, you have to delete it before it’s lost and
creates a memory leak. On the other hand, the smart pointers of
the standard library (std::unique_ptr, std::shared_ptr) provide
such a wrapper.
Smart pointers 113

1 SomeLimitedResource* resource = new SomeLimitedResource();


2 resouce->doIt(); // oops, it throws an exception...
3
4 delete resource; // this never gets called, so we have a \
5 leak

Applying RAII by smart pointers, it should be ok:

1 std::unique_ptr<SomeLimitedResource> resource =
2 std::make_unique<SomeLimitedResource>();
3
4 // even if it throws an exception, the resource gets rele\
5 ased
6 resouce->doIt();

Of you can write your own RAII handler:

1 class SomeLimitedResourceHandler {
2 public:
3 SomeLimitedResourceHandler(SomeLimitedResource* resourc\
4 e) :
5 m_resource(resource) {}
6
7 ~SomeLimitedResourceHandler() { delete m_resource; }
8
9 void doit() {
10 m_resource->doit();
11 }
12
13 private:
14 SomeLimitedResource* m_resource;
15 };
16
17
18 SomeLimitedResourceHandler resourceHandler(new SomeLimite\
Smart pointers 114

19 dResource());
20 // resource will be released even if there is an exception
21 resourceHandler.doit();

References:

• C++ Reference: RAII¹¹⁰


• Quora: What is Bjarne Stroustrup’s biggest regret in the
design history of C++?¹¹¹
• Wikipedia: Resource acquisition is initialization¹¹²

Question 52: When should we use


unique pointers?
If you need a smart pointer, by default, you should reach for
std::unique_ptr. It is a small, fast, move-only smart pointer for
managing resources with exclusive-ownership semantics.
They have the same size as a raw pointer, and for most operations,
they require the same amount of instructions.
As mentioned, it’s for exclusive ownership. Whatever it points to,
it also owns it. std::unique_ptr is a move only type, copying is
not allowed, there can be only one owner. There is no reference
counting, this makes it smaller and faster than the std::shared_-
ptr.

By default, resource destruction takes place via delete, but custom


deleters can be specified. Stateful deleters and function pointers
as deleters increase the size of std::unique_ptr objects. Resource
destruction happens as soon as the pointer goes out of scope.
In C++11, one can create a unique poitner as such:
¹¹⁰https://en.cppreference.com/w/cpp/language/raii
¹¹¹https://www.quora.com/What-is-Bjarne-Stroustrups-biggest-regret-in-the-design-
history-of-C
¹¹²https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
Smart pointers 115

1 std::unique_ptr<T> ptr (new T());


2 // or;
3 T* t = new T();
4 std::unique_ptr<T> ptr2 (t);

C++14 introduced std::make_unique to ease the creation:

1 std::unique_ptr<T> ptr = std::make_unique<T>();

The new way of pointer creation is safer, because before you could
accidentally pass in a raw pointer twice to a new unique pointer
like this:

1 T* t = new T();
2 std::unique_ptr<T> ptr (t);
3 std::unique_ptr<T> ptr2 (t);

A common use for std::unique_ptr is as a factory function return


type for objects in a hierarchy. In case it turns out that a shared
pointer would be a better fit, the conversion is really easy:

1 std::unique_ptr<T> unique = std::make_unique<T>();


2 std::shared_ptr<T> shared = std::move(unique);

References:

• C++ Reference¹¹³
• Converting unique_ptr to shared_ptr: Factory function exam-
ple¹¹⁴
• Effective Modern C++ by Scott Meyers¹¹⁵
¹¹³https://en.cppreference.com/w/cpp/memory/unique_ptr
¹¹⁴https://www.nextptr.com/question/qa1257940863/converting-unique_ptr-to-shared_ptr-
factory-function-example
¹¹⁵https://amzn.to/38gK5bd
Smart pointers 116

Question 53: What are the reasons


to use shared pointers?
Shared pointers brought C++ developers the advantages of two
worlds. It offers automatic cleanup (a.k.a. garbage collection) that
is applicable to all types with a destructor and it is predictable, not
like Garbage Collectors in other languages.
Compared to std::unique_ptr or to a raw pointer, std::shared_-
ptr objects are typically twice as big because they don’t just contain
a raw pointer, but they also contain another raw pointer to a
dynamically allocated memory area where the reference counting
happens.
By default, destruction happens via delete, but just like for
std::unique_ptr, custom deleters can be passed. It’s worth noting
that the type of the deleter has no effect on the type of the
std::shared_ptr.

Resource destruction happens as soon as the pointer goes out of


scope.
It’s available since C++11 and there are two ways to initalize a
shared pointer:

1 std::shared_ptr<T> ptr (new T());


2 std::shared_ptr<T> ptr2 = std::make_shared<T>();

The second way of pointer creation - via std::make_shared is safer,


because before you could accidentally pass in a raw pointer twice
and the cost of the dynamic allocation for the reference count
memory is avoided.
Avoid creating std::shared_ptrs from variables of raw pointer
type as it’s difficult to maintain, difficult to understand when the
pointed object would be destroyed.
Smart pointers 117

Use std::shared_ptr for shared-ownership resource management.


References:

• C++ Reference¹¹⁶
• Effective Modern C++ by Scott Meyers¹¹⁷

Question 54: When to use a weak


pointer?
A weak pointer - std::weak_ptr is a smart pointer that doesn’t
affect the object’s reference count and as such what it points to
might have been already destroyed.
std::weak_ptr is created from a shared_ptr and in case the pointed
at object gets destroyed, the weak pointer expires.

1 auto* sp = std::make_shared<T>();
2 std::weak_ptr<T> wp(sp);
3
4 //...
5 sp = nullptr;
6
7 if (wp.expired()) {
8 std::cout << "wp doesn't point to a valid object anymor\
9 e" << '\n';
10 }

In case you want to use it, you can either call lock() on it that either
returns a std::shared_ptr or nullptr in case the pointer is expired,
or you can directly pass a weak ptr to shared_ptr constructor.

¹¹⁶https://en.cppreference.com/w/cpp/memory/shared_ptr
¹¹⁷https://amzn.to/38gK5bd
Smart pointers 118

1 std::shared_ptr<T> sp = wp.lock();
2 std::shared_ptr<T> sp2(wp);

So how it can be useful?


It can be useful for cyclic ownerships, to break the cycle. Let’s say
that you have an instance of a Keyboard, a Logger, and a Screen
object. Both the Screen and the Keyboard has a shared pointer of
the Logger and the Logger should have a pointer to the Screen.
Keyboard -> Logger <–> Screen
What pointer can it use? If we use a raw pointer when the Screen
gets destroyed, the Logger will be still alive and the Keyboard still
has shared ownership on it. The Logger has a dangling pointer to
the Screen.
If it’s a shared pointer, there is a cyclic dependency between them,
they cannot be destroyed, there is a resource leak.
Here comes the std::weak_ptr to the rescue. There is still a dan-
gling pointer from Logger to the Screen, when the Screen gets
destroyed, but it can be easily detected as we have seen above.
Otherwise, it can be useful for caching and for the observer pattern.
Check out the referenced book to get more details.
Reference:

• Effective Modern C++ by Scott Meyers: Item 20¹¹⁸


¹¹⁸https://amzn.to/38gK5bd
Smart pointers 119

Question 55: What are the


advantages of std::make_shared and
std::make_unique compared to the new
operator?
Let’s start with a reminder. While std::make_shared was added to
the STL in C++11, std::make_unique was only added in C++14.
Compared to the direct use of new, make functions eliminate source
code duplication, improve exception safety, and std::make_shared
generates code that’s smaller and faster.

1 auto ptr = std::make_shared<T>(); // Prefer this


2 std::shared_ptr<T> ptr2(new T);

As you can see, with make functions, we have to type the type name
only once, we don’t have to duplicate it.
When you use new, if during construction there is an exception
thrown, in some circumstances, there might be a resource leak,
when the pointer has not yet been “processed” by the make func-
tion.
std::make_shared is also faster than simply using new as it allocates
memory only once to hold the object and the control block for ref-
erence counting. Whereas when you use new it uses two allocations.
Sadly, the mentioned make functions cannot be used if you want to
specify custom deleters. At least, they are not often used.
References:

• Effective Modern C++ by Scott Meyers: Item 21¹¹⁹


• Stackoverflow: Hot pas a deleter to make_shared¹²⁰
¹¹⁹https://amzn.to/38gK5bd
¹²⁰https://stackoverflow.com/questions/34243367/how-to-pass-deleter-to-make-shared
Smart pointers 120

Question 56: Should you use smart


pointers over raw pointers all the
time?
No, raw pointers are - although considered dangerous in many
cases - still have their place.
std::unique_ptr transfers and std::shared_ptr shares ownership.
In case a function has nothing to do with ownership, there should
be no need for it to take pointer parameters by a smart pointer.
Taking smart pointers in such cases will only make its API more
restrictive and the run-time cost higher.
At the same time, we should refrain from using the new key-
word. new almost always means that you should delete some-
where. Unless you are creating a std::unique_ptr in C++11 where
std::make_unique is not available.

To summarize, try not to use new. When it comes to ownership


use smart pointers and the related factory functions - unless some
special cases apply (check Effective Modern C++: Item 21). Other-
wise, if you don’t want to share or transfer ownership, just use a
raw pointer. It’ll keep the API more user-friendly and the run-time
performance faster.
References:

• Core Guidelines: F7¹²¹


• Effective Modern C++ by Scott Meyers (Item 21)¹²²
¹²¹https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f7-for-general-use-
take-t-or-t-arguments-rather-than-smart-pointers
¹²²https://amzn.to/38gK5bd
Smart pointers 121

Question 57: When and why should


we initialize pointers to nullptr?
If it’s sure that the pointer will get initialized, it doesn’t make
sense to pre-initialize with a nullptr. Approaching from the other
direction, you should initially assign nullptr to a variable in case
it’s not sure that you’d initialize it otherwise.
For example, the following piece of code might emit a warning
based on your compiler and its settings. And hopefully, you treat
warnings errors:

1 T* fun() {
2 T* t;
3 if (someCondition()) {
4 t = getT();
5 }
6 return t;
7 }
8 /*
9 Warnings by Clang:
10 prog.cc:22:6: warning: variable 't' is used uninitialized\
11 whenever 'if' condition is false [-Wsometimes-uninitiali\
12 zed]
13 if (someCondition()) {
14 ^~~~~~~~~~~~~~~
15 prog.cc:25:9: note: uninitialized use occurs here
16 return t;
17 ^
18 prog.cc:22:2: note: remove the 'if' if its condition is a\
19 lways true
20 if (someCondition()) {
21 ^~~~~~~~~~~~~~~~~~~~~
22 prog.cc:21:6: note: initialize the variable 't' to silenc\
23 e this warning
Smart pointers 122

24 T* t;
25 ^
26 = nullptr
27 */

But why is this a problem? What can go wrong?


When you don’t initialize a pointer and then try to use it, you have
3 possible problems:

• The pointer might point at a memory location that you don’t


have access to, in such cases it causes a segmentation fault
and your program crashes
• The pointer - accidentally - might point at some real data,
and if you don’t know what it’s pointing at, you might cause
unpredictable (and very hard to debug) changes to your data.
• You have no way to determine if the pointer has been ini-
tialized or not. How would you tell the difference between a
valid address and the address that just happened to be there
when you declared the pointer?

If you cannot initialize your pointer at declaration time, initializing


it always to nullptr seriously decreases or eliminates those prob-
lems:

• While it’s true that if we use the pointer it will still cause
a segmentation fault, but at least we can reliably test if it’s
nullptr and act accordingly. If it’s a random value, we don’t
know anything until it crashes.
• If we initialize it to nullptr, it will never point to any data,
therefore it won’t modify anything accidentally, only what it
meant to.
• Hence, we can deterministically tell if a pointer is initialized
or not and make decisions based on that.
Smart pointers 123

1 T* fun() {
2 T* t = nullptr;
3 if (someCondition()) {
4 t = getT();
5 }
6 return t; // No warnings anymore!
7 }

It’s a matter of style, but I’d personally prefer to avoid having to


initialize a pointer as nullptr. In those cases, I’d prefer to return
a nullptr right away and otherwise just initialize the pointer with
the correct value at declaration time.

1 T* fun() {
2 if (!someCondition()) {
3 return nullptr;
4 }
5 return getT();
6 }

PS: It’s even better to avoid the need of returning a nullptr.


Reference:

• Quora: Is the null pointer same as an uninitialized pointer?¹²³

¹²³https://www.quora.com/Is-the-null-pointer-same-as-an-uninitialized-pointer
References, universal
references, a bit of a
mixture
The next couple of questions will cover some mixture of references,
universal references, and even noexcept.

Question 58: What does std::move


move?
std::move doesn’t move anything. At runtime, it does nothing at
all. It doesn’t even generate a single byte of executable code.
std::move is in fact just a tool to cast whatever its input is to an
rvalue reference.
Hence, std::move is not a great name, probably rvalue_cast would
be better, but it’s called as it is. Let’s just remember that it doesn’t
move anything. It returns an rvalue reference and that is a candi-
date for a move operation. Applying std::move to an object tells the
compiler that the object is eligible to be moved from. That’s why
std::move has the name it does have: to make it easy to designate
objects that may be moved from.
It’s worth noting that moving from a const variable is not possible
as the move constructor and the move assignment can change the
object from where the move is performed. Yet if you try to move
from a const object, the compiler will say nothing. No compiler
warning not to say error. Move requests on const objects are silently
transformed into copy operations.
References, universal references, a bit of a mixture 125

References:

• C++ Reference: std::move¹²⁴


• Effective Modern C++ by Scott Meyers: Item 21¹²⁵

Question 59: What does std::forward


forward?
Just like std::move doesn’t move anything, std::forward doesn’t
forward anything either. Similarly, it does nothing at all at runtime.
It doesn’t even generate a single byte of executable code.
std::forward is also a cast, just like std::move. But how it is used?

The most common scenario for std::forward is a function template


that takes a universal reference parameter that is to be passed to
another function:

1 void process(MyClass&& rvalueArgument);


2
3 template<typename T>
4 void logAndProcess(T&& param) {
5 auto now = std::chrono::system_clock::now();
6 makeLogEntry("Calling 'process'", now);
7 process(std::forward(param));
8 }

It is also called perfect forwarding. It has two overloads.


One forwards lvalues as lvalues and rvalues as rvalues, while the
other is a conditional cast. It forwards rvalues as rvalues and
prohibits forwarding of rvalues as lvalues. Attempting to forward
an rvalue as an lvalue, is a compile-time error.
¹²⁴https://en.cppreference.com/w/cpp/utility/move
¹²⁵https://amzn.to/38gK5bd
References, universal references, a bit of a mixture 126

References:

• C++ Reference: std::forward¹²⁶


• Effective Modern C++ by Scott Meyers: Item 21¹²⁷

Question 60: What is the difference


between universal and rvalue
references?
If a function template parameter has type T&& for a deduced type T,
or if an object is declared using auto&&, the parameter or object is
a universal reference.

1 template<typename T>
2 void f(T&& param);
3
4 // universal reference
5 auto&& v2 = v; // universal reference

But what is a universal reference, you might ask. Universal ref-


erences correspond to rvalue references if they’re initialized with
rvalues. They correspond to lvalue references if they’re initialized
with lvalues. They are either this or that depending on what is
passed in.
If the form of the type declaration isn’t precisely type&&, or if type
deduction does not occur - there is no auto used - we have an rvalue
reference.

¹²⁶https://en.cppreference.com/w/cpp/utility/forward
¹²⁷https://amzn.to/38gK5bd
References, universal references, a bit of a mixture 127

1 void f(MyClass&& param); // rvalue reference


2
3 MyClass&& var1 = MyClass(); // rvalue reference
4
5 template<typename T>
6 void f(std::vector<T>&& param); // rvalue reference

The takeaway is that by knowing the differences between rvalue


and universal references, you can read source code more accurately.
Is this an rvalue type that can be bound only to rvalues or is this
a universal reference that can be bound to either rvalue or lvalue
references? This understanding will also avoid ambiguities when
you communicate with your colleagues (“I’m using a universal
reference here, not an rvalue reference…”)
References:

• ISOCpp.org¹²⁸
• Effective Modern C++ by Scott Meyers: Item 24¹²⁹

Question 61: What is reference


collapsing?
Reference collapsing can happen in four different scenarios:

• template instantiation
• auto type generation
• creation and use of typedefs and alias declarations
• using decltype.
¹²⁸https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
¹²⁹https://amzn.to/38gK5bd
References, universal references, a bit of a mixture 128

You are not allowed to declare a reference to a reference, but


compilers may produce them in the above-listed contexts. When
compilers generate references to references, reference collapsing
dictates what happens next.

1 int x;
2 auto& & rx = x; // error! can't declare reference to refe\
3 rence
4
5 typedef int& T;
6 // a has the type int&
7
8 T&& a; // (&& + & => &)
9 template <typename T> void func(T&& a);
10 auto fp = func<int&&>; // && of func + && of int => &&

There are two kinds of references (lvalue and rvalue), so there are
four possible reference-reference combinations:

• lvalue to lvalue
• lvalue to rvalue
• rvalue to lvalue
• rvalue to rvalue

If a reference to a reference arises in one of the four listed contexts,


the references collapse to one single reference according to this rule:

If either reference is an lvalue reference, the result is


an lvalue reference. Otherwise (i.e., if both are rvalue
references) the result is an rvalue reference.

Universal references are considered as rvalue references in contexts


where type deduction distinguishes lvalues from rvalues and where
reference collapsing occurs.
References:
References, universal references, a bit of a mixture 129

• IBM Knowledge Center: Reference collapsing (C++11)¹³⁰


• Effective Modern C++ by Scott Meyers: Item 28¹³¹

Question 62: When constexpr


functions are evaluated?
constexpr functions might be evaluated at compile-time, but it’s
not guaranteed. They can be executed both at runtime and at
compile time. It often depends on the compiler version and the
optimisation level.
If the value of a constexpr function is requested during compile
time with constexpr variable, then it will be executed at compile
time: constexpr auto foo = bar(42) where bar is a constexpr
function.
Also, if a constexpr function is executed in the context of a C-array
initialization or static assertion, it will be evaluated at compile time.
In case a constant is needed, but you provide only a runtime
function, the compiler will let you know.
It’s not a good idea to make all functions constexpr as most
computations are best done at run time. At the same time, it’s
worth noting that constexpr functions will be always threadsafe
and inlined.
References:

• C++ Core Guidelines: If a function may have to be evaluated


at compile-time, declare it constexpr¹³²
¹³⁰https://www.ibm.com/support/knowledgecenter/SSGH3R_13.1.2/com.ibm.xlcpp131.aix.
doc/language_ref/reference_collapsing.html
¹³¹https://amzn.to/38gK5bd
¹³²https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f4-if-a-function-may-
have-to-be-evaluated-at-compile-time-declare-it-constexpr
References, universal references, a bit of a mixture 130

• Modernes C++: Programming at Compile Time with const-


expr¹³³

Question 63: When should you


declare your functions as noexcept?
You should definitely put noexcept on every function written
completely in C or in any other language without exceptions. The
C++ Standard Library does that implicitly for all functions in the C
Standard Library.
Otherwise, you should use noexcept for functions that don’t throw
an exception, or if it throws, then you don’t mind letting the
program crash.
Here is a small code sample from ModernesC++¹³⁴ to show how to
use it:

1 void func1() noexcept; // does not throw


2 void func2() noexcept(true); // does not throw
3 void func3() throw(); // does not throw
4 void func4() noexcept(false); // may throw

But what does it mean that a function doesn’t throw an exception?


It means it cannot use any other function that throws, it is declared
as noexcept itself and it doesn’t use dynamic_cast to a reference
type.
The six generated special functions are implicitly noexcept func-
tions.
¹³³https://www.modernescpp.com/index.php/c-core-guidelines-programming-at-compile-
time-with-constexpr
¹³⁴https://www.modernescpp.com/index.php/c-core-guidelines-the-noexcept-specifier-
and-operator
References, universal references, a bit of a mixture 131

If an exception is thrown in spite of noexcept specifier being


present, std::terminate is called.
So you can use noexcept when it’s better to crash than actually
handling an exception, as the Core Guidelines also indicates.
Using noexcept can give hints both for the compiler to perform
certain optimizations and for the developers as well that they don’t
have to handle possible exceptions.
With the next few questions, we’ll discover the main features of
C++20. Stay tuned!
References:

• C++ Core Guidelines: F6¹³⁵


• C++ Core Guidelines: E12¹³⁶
• ModernesC++: C++ Core Guidelines: The noexcept Specifier
and Operator¹³⁷

¹³⁵https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f6-if-your-function-
may-not-throw-declare-it-noexcept
¹³⁶http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Re-noexcept
¹³⁷https://www.modernescpp.com/index.php/c-core-guidelines-the-noexcept-specifier-
and-operator
C++20
The next couple of questions are about some basic knowledge of
the latest C++ features from C++20. Knowing the answers proves
that you - at least - try to keep up with the changes.

Question 64: What are concepts in


C++?
Concepts are an extension to templates. They are compile-time
predicates that you can use to express a generic algorithm’s expec-
tations on its template arguments.
Concepts allow you to formally document constraints on templates
and have the compiler enforce them. As a bonus, you can also take
advantage of that enforcement to improve the compile time of your
program via concept-based overloading.
The main uses of concepts are:

• Introducing type-checking to template programming


• Simplified compiler diagnostics for failed template instantia-
tions
• Selecting function template overloads and class template
specializations based on type properties
• Constraining automatic type deduction

Here is how you can define concepts:


C++20 133

1 template<typename T>
2 concept integral = std::is_integral<T>::value;

Then you could use it as such:

1 auto add(integral auto a, integral auto b) {


2 return a+b;
3 }

What are the advantages of concepts?

• Requirements for templates are part of the interface.


• The overloading of functions or specialisation of class tem-
plates can be based on concepts.
• We get improved error messages because the compiler com-
pares the requirements of the template parameter with the
actual template arguments
• You can use predefined concepts or define your own.
• The usage of auto and concepts is unified. Instead of auto,
you can use the conceptName auto syntax.
• If a function declaration uses a concept, it automatically
becomes a function template. Writing function templates is,
therefore, as easy as writing a function.

References:

• Modernes C++: Concepts, the details¹³⁸


• Modernes C++: Defining Concepts¹³⁹
• CppReference¹⁴⁰
• Microsoft C++ Blog¹⁴¹
• Wikipedia¹⁴²
¹³⁸https://www.modernescpp.com/index.php/c-20-concepts-the-details
¹³⁹https://www.modernescpp.com/index.php/c-20-concepts-defining-concepts
¹⁴⁰https://en.cppreference.com/w/cpp/language/constraints
¹⁴¹https://devblogs.microsoft.com/cppblog/c20-concepts-are-here-in-visual-studio-2019-
version-16-3/
¹⁴²https://en.wikipedia.org/wiki/Concepts_(C%2B%2B)
C++20 134

Question 65: What are the available


standard attributes in C++?
First, what is an attribute? And how does it look like?
A simple one looks like this: [[attribute]]. But it can have
parameters ([[deprecated("because")]]) or a namespace
([[gnu::unused]]) or both.
Attributes provide the unified standard syntax for implementation-
defined language extensions, such as the GNU and IBM language
extensions. They can be used almost anywhere in a C++ program,
but our focus is not on them today.
We are interested in attributes defined by the C++ standard.
The first ones were introduced by C++11:

• [[noreturn]] indicates that a function does not return. It


doesn’t mean that it returns a void, but that it doesn’t return.
It can mean that it always throws an exception. Maybe
different ones based on their input.
• [[carries_dependency]] indicates that a dependency chain
in release-consume std::memory_order propagates in and out
of the function, which allows the compiler to skip unneces-
sary memory fence instructions.

Then C++14 added another type of attribute with two versions:

• [[deprecated]] and [[deprecated(reason)]] indicate that


the usage of that entity is discouraged. A reason can be
specified as a parameter.

C++17 fastened up and added three more.


C++20 135

• [[fallthrough]] indicates in a switch-case that a break or


return is missing on purpose. The fall through from one case
label to another is intentional
• [[nodiscard]] indicates that the return value of a function
should not be discarded - in other words, it must be saved
into a variable - otherwise, you’ll get a compiler warning
• [[maybe_unused]] suppresses compiler warnings on unused
entities, if any. For example, you’ll not get a compiler warning
for an unused variable if it was declared with the [[maybe_-
unused]] variable.

C++20 added another 4 attributes:

• [[nodiscard("reason")]] it’s the same as [[nodiscard]] but


with a reason specified.
• [[likely]] indicates to the compiler that a switch-case or an
if-else branch is likely to be executed more frequently than
the others and as such it lets the compiler optimize for that
evaluation path.
• [[unlikely]] has the same concept as [[likely]], but in this
case, such labelled paths are less likely to be executed than
the others.
• [[no_unique_address]] indicates that this data member need
not have an address distinct from all other non-static data
members of its class.

Reference:

• C++ Reference¹⁴³
¹⁴³https://en.cppreference.com/w/cpp/language/attributes
C++20 136

Question 66: What is 3-way


comparison?
The three-way comparison operator is also known as the starship
operator and it looks like this: lhs <=> rhs.
It helps to decide which operand is greater, lesser or whether they
are equal.
If you ever implemented comparison operators in C++, you know
what a menial, tedious task it is. You have to define six operators
(==, !=, <, <=, >, >=).
With C++20, you can simply =default the spaceship operator and it
will generate all the six constexpr and noexcept operators for you
and they perform lexicographical comparison.
For the exact rules on the supported types, check CppReference¹⁴⁴.
References:

• ModernesC++¹⁴⁵
• CppReference¹⁴⁶

Question 67: Explain what consteval


and constinit bring to C++?
C++11 introduced constexpr expressions that might be evaluated
at compile-time.
C++20 introduces two new related keywords, consteval and
constinit.

consteval can be used with functions:


¹⁴⁴https://en.cppreference.com/w/cpp/language/operator_comparison
¹⁴⁵https://www.modernescpp.com/index.php/c-20-the-three-way-comparison-operator
¹⁴⁶https://en.cppreference.com/w/cpp/language/operator_comparison
C++20 137

1 consteval int sqr(int n) {


2 return n * n;
3 }

consteval functions are guaranteed to be executed at compile-time,


thus they create compile-time constants. They cannot allocate or
deallocate data, nor they can interact with static or thread-local
variables.
constinit can be applied to variables with static storage duration
or thread storage duration. So a local or member variable cannot be
constinit. What it guarantees that the initialization of the variable
happens at compile-time.
It must be noted that while constexpr and const variables cannot
be changed once they are assigned, a constinit variable is not
constant, its value can change.
References:

• CppReference: consteval specifier¹⁴⁷


• CppReference: constinit specifier¹⁴⁸
• Modernes C++¹⁴⁹

Question 68: What are modules and


what advantages do they bring?
As we already discussed #include statements are basically textual
inclusions. The preprocessor macro replaces the #include statement
with the content of the file to be included.
Hence a simple hello world program can grow from around 100
bytes up to 13,000. Simply because of the inclusion of <iostream>.
¹⁴⁷https://en.cppreference.com/w/cpp/language/consteval
¹⁴⁸https://en.cppreference.com/w/cpp/language/constinit
¹⁴⁹https://www.modernescpp.com/index.php/c-20-consteval-and-constinit
C++20 138

All the header headers will be copied, even if you only want to use
one small function.
Modules, introduced by C++20, finally bring a solution. Importing
a module is basically free, unlike for inclusion, the order of imports
doesn’t matter.
With modules, you can easily structure your libraries and with
export qualifiers you can easily decide what you want to expose
and what not.
Thanks to the modules, there is no more need for separating header
and implementation files.
Here is a short example:

1 // math.cppm
2 export module math;
3
4 export int square(int n){
5 return n*n;
6 }
7
8 // main.cpp
9 import math;
10
11 int main(){
12 square(42);
13 }

For more details - there are a lot! - check out the references.
During the next seven days, we’ll learn about the special functions
of C++ classes and the related rules we should follow.
References:

• CppReference¹⁵⁰
¹⁵⁰https://en.cppreference.com/w/cpp/language/modules
C++20 139

• Microsoft Devblogs¹⁵¹
• ModernesC++¹⁵²

¹⁵¹https://devblogs.microsoft.com/cppblog/a-tour-of-cpp-modules-in-visual-studio/
¹⁵²https://www.modernescpp.com/index.php/c-20-modules
Special function and the
rules of how many?
Question 69: Explain the rule of
three
If a class requires a user-defined destructor, a user-defined copy
constructor, or a user-defined copy assignment operator, it almost
certainly requires all three.
When you return or pass an object by value, you manipulate a
container, etc., these member functions will be called. If they are
not user-defined, they are generated by the compiler (since C++98).
Since C++98 the compiler tries to generate

• a default constructor (T()), that calls the default constructor


of each class member and base class

-a copy constructor (T(const T& other)), that calls a copy construc-


tor on each member and base class

• a copy assignment operator (T& operator=(const T& other)),


that calls a copy assignment operator on each class member
and base class
• the destructor (∼T()), which calls the destructor of each
class member and base class. Note that this default-generated
destructor is never virtual (unless it is for a class inheriting
from one that has a virtual destructor).
Special function and the rules of how many? 141

In case you have a class holding on a raw or smart pointer,


a file descriptor, database connection, or other types managing
resources, there is a fair chance that the generated functions would
be incorrect and you should implement them by hand.
And here comes the rule of three, if you implement by hand
either the copy constructor, the copy assignment operator or the
destructor, the compiler will not generate those that you didn’t
write. So if you need to write any of these three, the assumption
is that you’ll need the rest as well.
This is the rule of three introduced by C++98 and tomorrow will
move on to the rule of 5.
References:

• C++ Reference¹⁵³
• Fluent C++¹⁵⁴
• Modernes C++¹⁵⁵

Question 70: Explain the rule of five


Alright, yesterday we talked about the rule of three. Do you
remember what it was about?
Let’s recap.
If you implement by hand either the copy constructor, the copy as-
signment operator or the destructor, the compiler will not generate
those that you didn’t write. It’s because the compiler assumes that
if you’d have to implement one of them, it’s almost for sure that
you’d need the others too.
¹⁵³https://en.cppreference.com/w/cpp/language/rule_of_three
¹⁵⁴https://www.fluentcpp.com/2019/04/19/compiler-generated-functions-rule-of-three-
and-rule-of-five/
¹⁵⁵https://www.modernescpp.com/index.php/c-core-guidelines-constructors-assignments-
and-desctructors
Special function and the rules of how many? 142

The rule of three was introduced by C++98, and the rule of five was
introduced by C++11.
What’s that extra two?
It’s about move semantics that was introduced by C++11. So if you
implement by hand any of the following special functions, then
none of the others will be generated. You have to take care of
implementing all of them:

• a copy constructor: T(const T&)


• a copy assignment: operator=(const T&)
• a move constructor: T(X&&)
• a move assignment: operator=(T&&)
• a destructor: ∼T()

Tomorrow will move on to the rule of zero.


References:

• C++ Reference¹⁵⁶
• Fluent C++¹⁵⁷
• Modernes C++¹⁵⁸

Question 71: Explain the rule of zero


The last two days, we talked about the rule of three introduced by
C++98 and then about the rule of five which was complemented by
the move semantics introduced by C++11.
Let’s repeat the rule of five:
¹⁵⁶https://en.cppreference.com/w/cpp/language/rule_of_three
¹⁵⁷https://www.fluentcpp.com/2019/04/19/compiler-generated-functions-rule-of-three-
and-rule-of-five/
¹⁵⁸https://www.modernescpp.com/index.php/c-core-guidelines-constructors-assignments-
and-desctructors
Special function and the rules of how many? 143

If you implement by hand any of the following special functions,


then none of the others will be generated. You have to take care of
implementing all of them:

• a copy constructor: T(const T&)


• a copy assignment: operator=(const T&)
• a move constructor: T(X&&)
• a move assignment: operator=(T&&)
• a destructor: ∼T()

Today we finish the mini-series of these rules with the short episode
of the rule of zero.
It’s the nickname of one of the rules defined by the C++ Core
Guidelines:

C.20: If you can avoid defining default operations, do¹⁵⁹

If all the members have all their special functions, you’re done, you
don’t need to define any, zero.

1 class MyClass {
2 public:
3 // ... no default operations declared
4 private:
5 std::string name;
6 std::map<int, int> rep;
7 };
8
9 MyClass mc; // default constructor
10 MyClass mc2 {nm}; // copy constructor
¹⁵⁹https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-
zero
Special function and the rules of how many? 144

As both std::map and std::string have all the special functions, none
is needed in MyClass
The idea is that a class needs to declare any of the special functions,
then it should deal exclusively with ownership and other classes
shouldn’t declare these special functions.
So keep in mind, that if you need any of the special functions,
implement all of them, but try not to need them in the first place.
References:

• C++ Reference¹⁶⁰
• Fluent C++¹⁶¹
• Modernes C++¹⁶²

Question 72: What does std::move


move?
std::move doesn’t move anything. At runtime, it does nothing at
all. It doesn’t even generate a single byte of executable code.
std::move is in fact just a tool to cast whatever its input is to an
rvalue reference.
Hence, std::move is not a great name, probably rvalue_cast would
be better, but it’s called as it is. Let’s just remember that it doesn’t
move anything. It returns an rvalue reference and that is a candi-
date for a move operation. Applying std::move to an object tells the
compiler that the object is eligible to be moved from. That’s why
std::move has the name it does: to make it easy to designate objects
that may be moved from.
¹⁶⁰https://en.cppreference.com/w/cpp/language/rule_of_three
¹⁶¹https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/
¹⁶²https://www.modernescpp.com/index.php/c-core-guidelines-constructors-assignments-
and-desctructors
Special function and the rules of how many? 145

It’s worth noting that moving from a const variable is not possible
as the move constructor and the move assignment can change the
object from where the move is performed. Yet if you try to move
from a const object, the compiler will say nothing. No compiler
warning not to say error. Move requests on const objects are silently
transformed into copy operations.
References:

• C++ Reference: std::move¹⁶³


• Effective Modern C++ by Scott Meyers: Item 21¹⁶⁴

Question 73: What is a destructor


and how can we overload it?
A destructor is a special member function of a class. It has the same
name as the class name and is also prefixed with a tilde symbol
(MyClass). If available, it is executed automatically whenever an
object goes out of scope.
A destructor has no parameters, it cannot be const, volatile or
static and just like constructors, it has no return type.

It is generated by the compiler by default, but you have to pay


attention as the rule of 5 applies. If any of the other 4 special
functions is implemented manually, the destructor will not be
generated. As a quick reminder, the special functions besides the
destructor are:

• copy constructor
• assignment operator
• move constructor
¹⁶³https://en.cppreference.com/w/cpp/utility/move
¹⁶⁴https://amzn.to/38gK5bd
Special function and the rules of how many? 146

• move assignment

A destructor is needed if the class acquires resources that have to


be released. Remember, you should write RAII class, meaning that
resources are acquired on construction and released on destruction.
This can be things like releasing connections, closing file handles,
saving transactions, etc.
As said a destructor has no parameter, it cannot be const, volatile
or static, and there can be only one destructor. Hence it cannot be
overloaded.
On the other hand, a destructor can be virtual, but that’s the topic
for tomorrow.
References:

• C++ Reference: destructor¹⁶⁵


• C++ Reference: RAII¹⁶⁶

Question 74: Should you explicitly


delete unused/unsupported special
functions or declare them as
private?
The first question to answer is why would you do any of the
options?
You might not want a class to be copied or moved, so you want to
keep related special functions unreachable for the caller. One option
is to declare them as private or protected and the other is that you
explicitly delete them.
¹⁶⁵https://en.cppreference.com/w/cpp/language/destructor
¹⁶⁶https://en.cppreference.com/w/cpp/language/raii
Special function and the rules of how many? 147

1 class NonCopyable {
2 public:
3 NonCopyable() {/*...*/}
4 // ...
5
6 private:
7 NonCopyable(const NonCopyable&); //not defined
8 NonCopyable& operator=(const NonCopyable&); //not defin\
9 ed
10 };

Before C++11 there was no other option than declaring the un-
needed special functions private and not implementing them. As
such, one could disallow copying objects (there was no move
back in time). The lack of implementation/definition helps against
accidental usages in members, friends, or when you disregard the
access specifiers. You’ll face a problem at linking time.
Since C++11 you can simply mark them deleted by declaring them
as = delete;

1 class NonCopyable {
2 public:
3 NonCopyable() {/*...*/}
4 NonCopyable(const NonCopyable&) = delete;
5 NonCopyable& operator=(const NonCopyable&) = delete;
6 // ...
7 private:
8 // ...
9 };

The C++11 way is a better approach because

• it’s more explicit than having the functions in the private


section which might only be an error
Special function and the rules of how many? 148

• in case you try to make a copy, you’ll already get an error at


compilation time

Deleted functions should be declared public, not private. It’s not a


mandate by the compiler, but some compilers might only complain
that you call a private function, not that it’s deleted.
References:

• C++ Reference¹⁶⁷
• Effective Modern C++ by Scott Meyers¹⁶⁸

Question 75: What is a trivial class in


C++?
When a class or struct in C++ has compiler-provided or explicitly
defaulted special member functions, then it is a trivial type. It
occupies a contiguous memory area. It can have members with
different access specifiers. In C++, the compiler is free to choose
how to order members in this situation. Therefore, you can memcpy
such objects but you cannot reliably consume them from a C
program. A trivial type T can be copied into an array of char or
unsigned char, and safely copied back into a T variable. Note that
because of alignment requirements, there might be padding bytes
between type members.
Trivial types have a trivial default constructor, trivial copy and
move constructor, trivial copy and move assignment operator and
trivial destructor. In each case, trivial means the constructor/oper-
ator/destructor is not user-provided and belongs to a class that has

• no virtual functions or virtual base classes,


¹⁶⁷https://en.cppreference.com/w/cpp/language/function#Deleted_functions
¹⁶⁸https://amzn.to/38gK5bd
Special function and the rules of how many? 149

• no base classes with a corresponding non-trivial constructor/-


operator/destructor
• no data members of class type with a corresponding non-
trivial constructor/operator/destructor

Whether a class is trivial or not, you can verify with the std::is_-
trivial trait class. It checks whether the class is trivially copyable
(std::is_trivially_copyable) and is trivially default constructible
std::is_trivially_default_constructible.

Some exaples:

1 #include <iostream>
2 #include <type_traits>
3
4 class A {
5 public:
6 int m;
7 };
8
9 class B {
10 public:
11 B() {}
12 };
13
14 class C {
15 public:
16 C() = default;
17 };
18
19 class D : C {};
20
21 class E {
22 virtual void foo() {}
23 };
24
Special function and the rules of how many? 150

25 int main() {
26 std::cout << std::boolalpha;
27 std::cout << std::is_trivial<A>::value << '\n';
28 std::cout << std::is_trivial<B>::value << '\n';
29 std::cout << std::is_trivial<C>::value << '\n';
30 std::cout << std::is_trivial<D>::value << '\n';
31 std::cout << std::is_trivial<E>::value << '\n';
32 }
33 /*
34 true
35 false
36 true
37 true
38 false
39 */

References:

• C++ Reference¹⁶⁹
• Microsoft Docs¹⁷⁰

Question 76: What advantages does


having a default constructor have?
We could say that a default constructor gives us the possibility of
simple object creation, but it’s not so much the case.
It’s true that it’s simple to create an object without passing any
parameters, but it’s only useful if the created object is fully usable. If
it still has to be initialized, then the simple creation is worth nothing
and in fact, it’s even misleading and harmful.
¹⁶⁹https://en.cppreference.com/w/cpp/types/is_trivial
¹⁷⁰https://docs.microsoft.com/en-us/cpp/cpp/trivial-standard-layout-and-pod-
types?view=msvc-160
Special function and the rules of how many? 151

On the other hand, many features of the standard library require


having a default constructor.
Think about std::vector, when you create a vector of 10 elements
(std::vector<T> ts(10);), 10 default constructed T objects will be
added to the new vector.
Having a default constructor also helps to define how an object
should look like that was just moved away from.
It’s worth noting that having a default constructor doesn’t mean
that you should define it. Whenever possible, let the compiler
generate it. So for example, if a default constructor only default
initializes data members, then you are better off using in-class mem-
ber initializers instead and let the compiler generate the default
constructor.
So whenever possible, you should have a default constructor, be-
cause it lets you use more language and standard library features,
but also make sure that a default constructor leaves a fully usable
object behind.
In the next three weeks, we’ll discover some parts of object-oriented
design, inheritance, how C++ handles polymorphism, the Curiously
Recurring Template Pattern, etc. Stay tuned!
References:

• Core Guidelines: C43¹⁷¹


• Core Guidelines: C45¹⁷²

¹⁷¹https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c43-ensure-that-a-
copyable-value-type-class-has-a-default-constructor
¹⁷²https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c45-dont-define-a-
default-constructor-that-only-initializes-data-members-use-in-class-member-initializers-
instead
Object oriented design,
inheritance,
polymorphism
During the next approximately 20 questions, we’ll discover some
parts of object-oriented design, inheritance, how C++ handles
polymorphism, the Curiously Recurring Template Pattern, etc.

Question 77: What are the


differences between a class and a
struct?
In C++, there is very little difference between classes and structs.
Specifically, the default accessibility of member variables and
methods are public in a struct and private in a class. Nothing more.
In practice, many programmers use the struct type as a storage class,
only. This is perhaps a throwback to C, where structures did not
support functions (or methods).
On the contrary, in C++ structs can have both functions, construc-
tors, even virtual functions. They can use inheritance, they can be
templated, exactly like a class.
When we speak about default visibility, it’s worth mentioning that
it applies to inheritance as well. When you write class Derived
: Base ... you get private inheritance, on the other hand, when
you write struct Derived: Base ... what you have is public
inheritance.
Object oriented design, inheritance, polymorphism 153

As we see, there is not much technical difference between the two,


but on the other hand according to the established conventions
what we express with one and the other is quite different.
A struct is a bundle of related elements¹⁷³, usually without any logic
encapsulated. It’s only there to effectively group things, to raise the
abstraction.
At the same time, a class usually does things. It encapsulates data
and the related logic. It offers an interface, at a level, it separates
the interface from the implementation. As a general rule of thumb,
if you have at least one private member, it’s better to use a class.
There are the following Core guidelines discussing this topic more
in detail:

• C.1: Organize related data into structures (structs or


classes)¹⁷⁴
• C.2: Use class if the class has an invariant; use struct if the
data members can vary independently¹⁷⁵
• C.3: Represent the distinction between an interface and an
implementation using a class¹⁷⁶
• C.8: Use class rather than struct if any member is non-
public¹⁷⁷
¹⁷³https://www.fluentcpp.com/2017/06/13/the-real-difference-between-struct-class/
¹⁷⁴http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#a-namerc-orgac1-
organize-related-data-into-structures-structs-or-classes
¹⁷⁵http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#a-namerc-structac2-use-
class-if-the-class-has-an-invariant-use-struct-if-the-data-members-can-vary-independently
¹⁷⁶http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#a-namerc-interfaceac3-
represent-the-distinction-between-an-interface-and-an-implementation-using-a-class
¹⁷⁷http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#a-namerc-classac8-use-
class-rather-than-struct-if-any-member-is-non-public
Object oriented design, inheritance, polymorphism 154

Question 78: What is constructor


delegation?
Constructor delegation means that one constructor can call another
one from the same class. This is something that is possible in C++
since C++11.
Constructor delegation helps to simplify the code by removing code
duplication for class initialization.

1 class T {
2 public:
3 T() : T(0, ""){}
4 T(int iNum, std::string iText) : num(iNum), text(iText)\
5 {};
6 private:
7 int num;
8 std::string text;
9 };

In the above example, we can see that the default constructor


simply calls the constructor that takes 2 arguments. This becomes
practical when you have more members and you have to add one
more. You have to update the constructor that directly initializes
all the members, and the compiler will gently remind you of all the
places where you have to pass in the extra variable.

Question 79: Explain the concept of


covariant return types and show a
use-case where it comes in handy!
Using covariant return types for a virtual function and for all its
overridden versions means that you can replace the original return
Object oriented design, inheritance, polymorphism 155

type with something narrower, in other words, with something


more specialized.
Let’s say you have a CarFactoryLine producing Cars. The special-
ization of these factory lines might produce SUVs, SportsCars, etc.
How do you represent it in code? The obvious way is still having
the return type as a Car pointer.

1 class CarFactoryLine {
2 public:
3 virtual Car* produce() {
4 return new Car{};
5 }
6 };
7
8 class SUVFactoryLine : public CarFactoryLine {
9 public:
10 virtual Car* produce() override {
11 return new SUV{};
12 }
13 };

This way, getting an SUV* requires a dynamic cast.

1 SUVFactoryLine sf;
2 Car* car = sf.produce();
3 SUV* suv = dynamic_cast<SUV*>(car);

Instead, we directly return an SUV*


Object oriented design, inheritance, polymorphism 156

1 class Car {
2 public:
3 virtual ~Car() = default;
4 };
5
6 class SUV : public Car {};
7
8 class CarFactoryLine {
9 public:
10 virtual Car* produce() {
11 return new Car{};
12 }
13 };
14
15 class SUVFactoryLine : public CarFactoryLine {
16 public:
17 virtual SUV* produce() override {
18 return new SUV{};
19 }
20 };

So that you can simple do this:

1 SUVFactoryLine sf;
2 SUV* car = sf.produce();

In C++, in a derived class, in an overridden function, you don’t have


to return the same type as in the base class, but you can return a
covariant return type. In other words, you can replace the original
type with a “narrower” one, in other words, with a more specified
data type.
Object oriented design, inheritance, polymorphism 157

Question 80: What is the difference


between function overloading and
function overriding?
Function overriding is a term related to polymorphism. If you
declare a virtual function in a base class with a certain implementa-
tion, in a derived class you can override its behaviour in case it uses
the very same signature. In case, the virtual keyword is missing in
the base class, it’s still possible to create a function with the same
signature in the derived class, but it doesn’t override it. Since C++11
there is also the override specifier which helps you to make sure that
you didn’t mistakenly fail to override a base class function. You can
find more details here¹⁷⁸.
Function overriding enables you to provide specific implementa-
tion of the function that is already defined in its base class.
On the other hand, overloading has nothing to do with poly-
morphism. When you have two functions with the same name,
same return type, but different numbers or types of parameters, or
qualifiers.
Here is an example for overloading based on parameters

1 void myFunction(int a);


2 void myFunction(double a);
3 void myFunction(int a, int b);

And here is another example based on qualifiers:

¹⁷⁸http://sandordargo.com/blog/2018/07/05/cpp-override
Object oriented design, inheritance, polymorphism 158

1 class MyClass {
2 public:
3 void doSomething() const;
4 void doSomething();
5 };

Or actually, it is possible to overload your function based on


whether their hosting objects are rvalue of lvalue references.

1 class MyClass {
2 public:
3 // ...
4
5 void doSomething() &; // used when *this is a lvalue
6 void doSomething() &&; // used when *this is a rvalue
7 };

You can learn more about this here¹⁷⁹.

Question 81: What is the override


keyword and what are its
advantages?
The override specifier will tell both the compiler and the reader
that the function where it is used is actually overriding a method
from its base class.
It tells the reader that “this is a virtual method, that is overriding a
virtual method of the base class.”
Use it correctly and you see no effect:

¹⁷⁹http://sandordargo.com/blog/2018/11/25/override-r-and-l0-values
Object oriented design, inheritance, polymorphism 159

1 class Base {
2 virtual void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // OK: Derived::foo overrides Base\
7 ::foo
8 };

But it will help you revealing problems with constness:

1 class Base {
2 virtual void foo();
3 void bar();
4 };
5
6 class Derived : Base {
7 void foo() const override; // Error: Derived::foo does \
8 not override Base::foo
9 // It tries to override Base\
10 ::foo const that doesn't exist
11 };

Let’s not forget that in C++, methods are non-virtual by default.


If we use override, we might find that there is nothing to override.
Without the override specifier we would just simply create a brand
new method. No more base methods are forgotten to be declared
as virtual if you use consistently the override specifier.
Object oriented design, inheritance, polymorphism 160

1 class Base {
2 void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // Error: Base::foo is not virtual
7 };

We should also keep in mind that when we override a method - with


or without the override specifier - no conversions are possible:

1 class Base {
2 public:
3 virtual long foo(long x) = 0;
4 };
5
6 class Derived: public Base {
7 public:
8 // error: 'long int Derived::foo(int)' marked override,\
9 but does not override
10 long foo(int x) override {
11 // ...
12 }
13 };

In my opinion, using the override specifier from C++11 is part of


clean coding principles. It reveals the author’s intentions, it makes
the code more readable and helps to identify bugs at build time. Use
it without moderation!
Object oriented design, inheritance, polymorphism 161

Question 82: Explain what is a friend


class or a friend function
Sometimes, there is a need for allowing a particular class to access
private or protected members of our class. Loosen the access level
of that member globally would be way too much.
Unless you can tweak the design to remove this need, the solution is
a friend class, which is capable of accessing the protected as well as
the private members of the class in which it is declared as a friend.

1 #include <iostream>
2
3 class A {
4 public:
5 void foo() {
6 std::cout << "foo\n";
7 }
8
9 private:
10 void bar() {
11 std::cout << "bar\n";
12 }
13
14 friend class B;
15 };
16
17 class B {
18 public:
19 void doIt() {
20 A a;
21 a.foo();
22 a.bar(); // B is a friend class of A, so it has access\
23 to A::b
24 }
Object oriented design, inheritance, polymorphism 162

25 }
26
27 int main() {
28 A a;
29 a.foo();
30 // a.bar();
31 // this would fail to compile as A::bar is private
32 B b;
33 b.doIt()
34 }

Similarly to the friend class, a friend function is able to access


private and protected class members. A friend function can either
be a free function or a method of a class.

1 #include <iostream>
2
3 class A {
4 public:
5 void foo() {
6 std::cout << "foo\n";
7 }
8
9 private:
10 void bar() {
11 std::cout << "bar\n";
12 }
13
14 friend void freeFunction();
15 };
16
17 void freeFunction() {
18 A a;
19 a.foo();
20 a.bar(); // freeFunction is a friend function of A, so \
Object oriented design, inheritance, polymorphism 163

21 it has access to freeFunction


22 }
23
24 int main() {
25 A a;
26 a.foo();
27 // a.bar(); // this would fail to compiler as A::bar is\
28 private
29 freeFunction();
30 }

Some important points about friend classes and friend functions:

• Friendship is not inherited


• Friendship isn’t mutual i.e. if some class called Friend is a
friend of some other class called NotAFriend then NotAFriend
doesn’t automatically become a friend of the Friend class
• The total number of friend classes and friend functions should
be limited in a program as the overabundance of the same
might lead to a depreciation of the concept of encapsulation
of separate classes, which is an inherent and desirable quality
of object-oriented programming
• A common use-case for friend classes and functions is unit-
testing. The test class becomes a friend of the class under test

Question 83: What are default


arguments? How are they evaluated
in a C++ function?
A default parameter is a value that is assigned to a parameter while
declaring a function.
Object oriented design, inheritance, polymorphism 164

A default argument allows a function to be called without providing


one or more trailing arguments.
The default value for a parameter is indicated at function decla-
ration time at the parameter list. We simply assign a value to the
parameter in the function declaration.
Here is an example:

1 int calculateArea(int a, int b);


2 int calculateArea(int a, int b=2);
3 int calculateArea(int a=3, int b=2);

These default values are used if one or more arguments are left
blank while calling the function - according to the number of left
blank arguments.
Let’s see a complete example. If the value is not passed for any of
the parameters during the function call, then the compiler uses the
default value(s) provided. If a value is specified, then the default
value is stepped on and the passed value is used.

1 #include <iostream>
2
3 int calculateArea(int a=3, int b=2) {
4 return a*b;
5 }
6
7 int main() {
8 std::cout << calculateArea(6, 4) << '\n';
9 std::cout << calculateArea(6) << '\n';
10 std::cout << calculateArea() << '\n';
11 }
12 /*
13 24
14 12
Object oriented design, inheritance, polymorphism 165

15 6
16 */

As shown in the above code, there are three calls to calculateArea


function. In the first call, we pass both arguments so the default
values are overridden by the caller, in the second only one, so the
last parameter defaults. In the last call, we provide no argument, so
both parameters take their default values.
In a function declaration, after a parameter with a default argument,
all subsequent parameters must have a default argument supplied
in this or a previous declaration from the same scope…unless the
parameter was expanded from a parameter pack, and keep in mind
that ellipsis(...) is not a parameter.
This means that that the int calculateArea(int a=5, int b);
would not compile. On the other hand, int g(int n = 0, ...)
would compile as ellipsis does not count as a parameter.
Default arguments are only allowed in the parameter lists of func-
tion declarations and lambda-expressions, (since C++14) and are
not allowed in the declarations of pointers to functions, references
to functions, or in typedef declarations.
One last thing to note is that you should always declare your
defaults in the header, not in the cpp file.

Question 84: What is this pointer


and can we delete it?
The this pointer is a constant pointer that holds the memory
address of the current object.
Whenever we call a member function through its object, the com-
piler secretly passes the address of the calling object as the first
parameter. Therefore this is available as a local variable within
Object oriented design, inheritance, polymorphism 166

the body of all non-static functions. The this pointer is not avail-
able in static member functions as static member functions can
be called without any object (only with the class name such as
MyClass::foo()).

Ideally, the delete operator should not be used for this pointer.
However, if it is used, then you must take into account the following
points.

• delete operator works only for objects allocated on the


heap (using operator new), if it’s allocated on the stack the
behaviour is undefined, your application might crash.

1 class A {
2 public:
3 void fun() {
4 delete this;
5 }
6 };
7
8 int main() {
9 A *aPtr = new A;
10 aPtr->fun(); // a valid call
11
12 // make ptr NULL to make sure that things are not acces\
13 sed using ptr.
14 aPtr = nullptr;
15
16 A a;
17 a.fun(); // undefined behaviour
18 }

• Once delete this is done, any member of the deleted object


should not be accessed after deletion.
Object oriented design, inheritance, polymorphism 167

1 #include <iostream>
2
3 class A {
4 public:
5 void fun() {
6 delete this;
7 // undefined behaviour, your application might crash
8 std::cout << x << '\n';
9 }
10 private:
11 int x{0};
12 };
13
14 int main() {
15 A a;
16 a.fun();
17 }

The best thing is to not do delete this at all because it is easy to


accidentally access member variables after the deletion. Besides, the
caller might not realize your object has self-destructed.
Also, delete this is a “code smell” indicating that your code
might not have an asymmetric strategy for object ownership (who
allocates and who deletes).
When still used, It is usually found in reference-counted classes
that, when the ref-count is decremented to 0, the DecrementRef-
Count()/Release()/whatever member function calls delete this.
Object oriented design, inheritance, polymorphism 168

Question 85: What is virtual


inheritance in C++ and when should
you use it?
Virtual inheritance is a C++ technique that ensures only one copy of
a base class’s member variables is inherited by grandchild derived
classes. Without virtual inheritance, if two classes B and C inherit
from class A, and class D inherits from both B and C, then D will
contain two copies of A’s member variables: one via B, and one via
C. These will be accessible independently, using scope resolution.
Instead, if classes B and C inherit virtually from class A, then
objects of class D will contain only one set of the member variables
from class A.
As you probably guessed, this technique is useful when you have
to deal with multiple inheritance and you can solve the infamous
diamond inheritance.
In practice, virtual base classes are most suitable when the classes
that derive from the virtual base, and especially the virtual base
itself, are pure abstract classes. This means the classes above the
“join class” (the one in the bottom) have very little if any data.
Consider the following class hierarchy to represent the diamond
problem, though not with pure abstracts.
Object oriented design, inheritance, polymorphism 169

1 struct Person {
2 virtual ~Person() = default;
3 virtual void speak() {}
4 };
5
6 struct Student: Person {
7 virtual void learn() {}
8 };
9
10 struct Worker: Person {
11 virtual void work() {}
12 };
13
14 // A teaching assistant is both a worker and a student
15 struct TeachingAssistant: Student, Worker {};
16 TeachingAssistant ta;

As declared above, a call to aTeachingAssistant.speak() is am-


biguous because there are two Person (indirect) base classes in
TeachingAssistant, so any TeachingAssistant object has two dif-
ferent Person base class subobjects. So an attempt to directly bind
a reference to the Person subobject of a TeachingAssistant object
would fail, since the binding is inherently ambiguous:

1 TeachingAssistant ta;
2 Person& a = ta; // error: which Person subobject should \
3 a TeachingAssistant cast into,
4 // a Student::Person or a Worker::Person?

To disambiguate, one would have to explicitly convert ta to either


base class subobject:
Object oriented design, inheritance, polymorphism 170

1 TeachingAssistant ta;
2 Person& student = static_cast(ta);
3 Person& worker = static_cast(ta);

In order to call speak(), the same disambiguation, or explicit qualifi-


cation is needed: static_cast<student&>(ta).speak()</student&>
or static_cast<worker&>(ta).speak()</worker&> or alternatively
ta.Student::speak() and ta.Worker::speak(). Explicit qualifica-
tion not only uses an easier, uniform syntax for both pointers and
objects but also allows for static dispatch, so it would arguably be
the preferable method.
In this case, the double inheritance of Person is probably unwanted,
as we want to model that the relation (TeachingAssistant is a
Person) exists only once; that a TeachingAssistant is a Student
and is a Worker does not imply that it is a Person twice (unless
the TA is schizophrenic): a Person base class corresponds to a
contract that TeachingAssistant implements (the “is a” relation-
ship above really means “implements the requirements of”), and
a TeachingAssistant only implements the Person contract once.
The real-world meaning of “only once” is that TeachingAssistant
should have only one way of implementing speak, not two
different ways, depending on whether the Student view of
the TeachingAssistant is eating, or the Worker view of the
TeachingAssistant. (In the first code example we see that speak()
is not overridden in either Student or Worker, so the two Person
subobjects will actually behave the same, but this is just a
degenerate case, and that does not make a difference from the C++
point of view.)
If we introduce virtual to our inheritance as such, our problems
disappear.
Object oriented design, inheritance, polymorphism 171

1 struct Person {
2 virtual ~Person() = default;
3 virtual void speak() {}
4 };
5
6 // Two classes virtually inheriting Person:
7 struct Student: virtual Person {
8 virtual void learn() {}
9 };
10
11 struct Worker: virtual Person {
12 virtual void work() {}
13 };
14
15 // A teaching assistant is still a student and the worker
16 struct TeachingAssistant: Student, Worker {};

Now we can easily call speak().


The Person portion of TeachingAssistant::Worker is now the same
Person instance as the one used by TeachingAssistant::Student,
which is to say that a TeachingAssistant has only one,
shared, Person instance in its representation and so a call
to TeachingAssistant::speak is unambiguous. Additionally,
a direct cast from TeachingAssistant to Person is also
unambiguous, now that there exists only one Person instance
which TeachingAssistant could be converted to.
This can be done through vtable pointers. Without going into
details, the object size increases by two pointers, but there is only
one Person object behind and no ambiguity.
You must use the virtual keyword in the middle level of the
diamond. Using it at the bottom doesn’t help.
You can find more detail at the Core Guidelines¹⁸⁰\nAddition
¹⁸⁰https://isocpp.org/wiki/faq/multiple-inheritance
Object oriented design, inheritance, polymorphism 172

reference here¹⁸¹.

Question 86: Should we always use


virtual inheritance? If yes, why? If
not, why not?
The answer is definitely no, we shouldn’t use virtual inheritance
all the time. According to an idiomatic answer, one of the key
C++ characteristics is that you should only pay for what you use.
And if you don’t need to solve the problems addressed by virtual
inheritance, you should rather not pay for it.
Virtual inheritance is almost never needed. It addresses the dia-
mond inheritance problem that we saw just yesterday. It can only
happen if you have multiple inheritance, and in case you can avoid
it, you don’t have the problem to solve. In fact, many languages
don’t even have this feature.
So let’s see the main drawbacks.
Virtual inheritance causes troubles with object initialization and
copying. Since it is the “most derived” class that is responsible
for these operations, it has to be familiar with all the intimate
details of the structure of base classes. Due to this, a more complex
dependency appears between the classes, which complicates the
project structure and forces you to make some additional revisions
in all those classes during refactoring. All this leads to new bugs
and makes the code less readable.
Troubles with type conversions may also be a source of bugs. You
can partly solve the issues by using the expensive dynamic_cast
wherever you used to use static_cast. Unfortunately, dynamic_-
cast is much slower, and if you have to use it too often in your code,

¹⁸¹https://en.wikipedia.org/wiki/Virtual_inheritance
Object oriented design, inheritance, polymorphism 173

it means that your project’s architecture has quite some room for
improvement.

Question 87: What does a strong


type mean and what advantages
does it have?
A strong type carries extra information, a specific meaning through
its name¹⁸². While you can use booleans or strings everywhere,
the only way they carry can carry meaning is the name of their
instances.
The main advantages of strong typing are readability and safety.
If you look at this function signature, perhaps you think it’s alright:

1 Car::Car(unit32_t horsepower, unit32_t numberOfDoors,


2 bool isAutomatic, bool isElectric);

It has relatively good names, so what is the issue?


Let’s look at a possible instantiation.

1 auto myCar{Car(96, 4, false, true)};

Yeah, what? God knows… And you if you take your time to actually
look up the constructor and do the mind mapping. Some IDEs can
help you visualizing parameter names, like if they were Python-
style named parameters, but you shouldn’t rely on that.
Of course, you could name the variables as such:

¹⁸²https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/
Object oriented design, inheritance, polymorphism 174

1 constexpr unit32_t horsepower = 96;


2 constexpr unit32_t numberOfDoors = 4;
3 constexpr bool isAutomatic = false;
4 constexpr bool isElectric = false;
5
6 auto myCar = Car{horsepower, numberOfDoors,
7 isAutomatic, isElectric};

Now you understand right away which variable represents what.


You have to look a few lines upper to actually get the values, but
everything is in sight. On the other hand, this requires willpower.
Discipline. You cannot enforce it. Well, you can be a thorough code
reviewer, but you won’t catch every case and anyway, you won’t
be there all the time.
Strong typing is there to help you!
Imagine the signature as such:

1 Car::Car(Horsepower hp, DoorsNumber numberOfDoors,


2 Transmission transmission, Fuel fuel);

Now the previous instantiation could look like this:

1 auto myCar{Car{Horsepower{98u}}, DoorsNumber{4u},


2 Transmission::Automatic, Fuel::Gasoline};\n

This version is longer and more verbose than the original version -
which was quite unreadable -, but much shorter than the one where
introduced well-named helpers for each parameter
So one advantage of strong typing is readability and one other
is safety. It’s much harder to mix up values. In the previous
examples, you could have easily mixed up door numbers with
performance, but by using strong typing, that would actually lead
to a compilation error.
References:
Object oriented design, inheritance, polymorphism 175

• Fluent Cpp¹⁸³
• SandorDargo’s Blog¹⁸⁴
• Correct by Construction: APIs That Are Easy to Use and Hard
to Misuse - Matt Godbolt¹⁸⁵

Question 88: What are user-defined


literals?
User-defined literals allow integer, floating-point, character, and
string literals to produce objects of user-defined type by defining a
user-defined suffix.
User-defined literals can be used with integer, floating-point, char-
acter and string types.
They can be used for conversions, you can write a degrees-to-radian
converter:

1 constexpr long double operator"" _deg ( long double deg )\


2 {
3 return deg * 3.14159265358979323846264L / 180;
4 }
5
6 //...
7 std::cout << "50 degrees as radians: " << 50.0_deg << '\n\
8 ';
9 // 50 degrees as radians: 0.872665\n

But what is probably more interesting is how it can help readability


and safety when you use strong types.
Let’s take this example from yesterday’s lesson:
¹⁸³https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/
¹⁸⁴https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
¹⁸⁵https://www.youtube.com/watch?v=nLSm3Haxz0I
Object oriented design, inheritance, polymorphism 176

1 auto myCar{Car{Horsepower{98u}}, DoorsNumber{4u},


2 Transmission::Automatic, Fuel::Gasoline};

It’s not very probable that you’d mix up the number of doors with
the power of the engine, but there can be other systems to measure
performance or other numbers taken by the Car constructor.
A very readable and safe way to initialize Car objects with hard-
coded values (so far example in unit tests) is via user-defined
literals:

1 //...
2 Horsepower operator"" _hp(int performance) {
3 return Horsepower{performance};
4 }
5
6 DoorsNumber operator"" _doors(int numberOfDoors) {
7 return DoorsNumber{numberOfDoors};
8 }
9
10 //...
11 auto myCar{Car{98_hp, 4_doors,
12 Transmission::Automatic, Fuel::Gasoline};

Of course, the number of ways to use user-defined literals is limited


only by your imagination, but numerical conversions, helping
strong typing are definitely two of the most interesting ones.
References:

• C++ Reference¹⁸⁶
• Modernes C++¹⁸⁷
¹⁸⁶https://en.cppreference.com/w/cpp/language/user_literal
¹⁸⁷https://www.modernescpp.com/index.php/user-defined-literals
Object oriented design, inheritance, polymorphism 177

Question 89: Why shouldn’t we use


boolean arguments?
Because it reduces readability on the client-side.
Let’s say you have such a function signature:

1 placeOrder(std::string shareName, bool buy,


2 int quantity, double price);

In the implementation of this function, it will be all fine, you might


have something like this somewhere in it:

1 //...
2 if (buy) {
3 // do buy
4 // ...
5 } else {
6 // do sell
7 // ...
8 }

That’s maybe not ideal, but it is readable.


On the other hand, think about the client-side:

1 placeOrder("GOOG", true, 1000, 100.0);

What the hell true means? What if we send false? You don’t
know without jumping to the signature. While modern IDEs can
help you with tooltips showing the function signature even the
documentation - if available -, we cannot take that for granted,
especially for C++ where this kind of tooling is still not at the level
of other popular languages.
Object oriented design, inheritance, polymorphism 178

And even if you look it up, it’s so easy to overlook and mix up true
with false.
Instead, let’s use enumerations:

1 enum class BuyOrSell {


2 Buy, Sell
3 };
4
5 placeOrder("GOOG", BuyOrSell::Buy, 1000, 100.0);

Using enumerations instead of booleans slightly increases the num-


ber of lines in our codebase (well in this case with three lines), but
it has a much bigger positive effect on readability. Both on the API
side and on the client-side, the intentions become super clear with
this technique.
Reference:

• Matt Godbolt: Correct by Construction¹⁸⁸

Question 90: Distinguish between


shallow copy and deep copy
A shallow copy does memory dumping bit-by-bit from one object
to another, in other words, it copies all of the member field values.
A deep copy is a field by field copy from one object to another.
The big difference is that a shallow copy may not do what you
want for members that are not storing values, but pointers to values.
In other words, for members pointing to dynamically allocated
memory. A shallow copy of those means that no new memory will
¹⁸⁸https://www.youtube.com/watch?v=nLSm3Haxz0I
Object oriented design, inheritance, polymorphism 179

be allocated and in fact, if you modify such a member through one


object, the changes will be visible from all the copies.
The default copy constructor and assignment operator make shal-
low copies. In case, a class has a reference or pointer members, there
is a fair chance that you have written the copy constructor yourself
and you should also write the assignment operator.
If you have to do so, don’t forget about the rule of five¹⁸⁹. Imple-
menting one of the special functions (such as the just mentioned
copy constructor or the assignment operator) will likely make you
implement all the other special functions.
To sum up, default copy constructor and default assignment op-
erators do memberwise shallow copies, which is fine for classes
that contain no dynamically allocated variables. On the other hand,
classes with dynamically allocated variables need to have a copy
constructor and assignment operator that do a deep copy.
References:

• Fluent C++¹⁹⁰
• Fredosaurus.com¹⁹¹
• LearnCpp¹⁹²
• Tutorials Point¹⁹³
¹⁸⁹https://www.fluentcpp.com/2019/04/19/compiler-generated-functions-rule-of-three-
and-rule-of-five/
¹⁹⁰https://www.fluentcpp.com/2019/04/19/compiler-generated-functions-rule-of-three-
and-rule-of-five/
¹⁹¹http://www.fredosaurus.com/notes-cpp/oop-condestructors/shallowdeepcopy.html
¹⁹²https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/
¹⁹³https://www.tutorialspoint.com/cplusplus/cpp_interview_questions.htm
Object oriented design, inheritance, polymorphism 180

Question 91: Are class functions


taken into consideration as part of
the object size?
No, only the class member variables determine the size of the
respective class object. The size of an object is at least the sum of
the sizes of its data members. It will never be smaller than this.
How can it be more?
The compiler might insert padding between data members to
ensure that each data member meets the alignment requirements
of the platform. Some platforms are very strict about alignment,
more than the others, yet in general, code with proper alignment
will perform significantly better. Therefore the optimization setting
might affect the object size.
Static members are not part of the class object and as such, they are
not included in the object’s layout, they don’t add up to the object’s
size.
Virtual functions and inheritance give some complexity to the size
calculation, we’ll get back to it another day.
sizeof(myObj) or sizeof(MyClass) will always tell you the proper
size of an object.

Question 92: What does dynamic


dispatch mean?
In computer science, dynamic dispatch is the process of selecting
which implementation of a polymorphic operation (method or
function) to call at run time. It is commonly employed in, and
considered a prime characteristic of, object-oriented programming
(OOP) languages and systems.
Object oriented design, inheritance, polymorphism 181

Dynamic dispatch contrasts with static dispatch, in which the


implementation of a polymorphic operation is selected at compile
time. The purpose of dynamic dispatch is to defer the selection of an
appropriate implementation until the run time type of a parameter
(or multiple parameters) is known.
Dynamic dispatch is different from late or dynamic binding. A
polymorphic operation has several implementations, all associated
with the same name. Bindings can be made at compile time or (with
late binding) at run time. With dynamic dispatch, one particular
implementation of an operation is chosen at run time. While
dynamic dispatch does not imply late binding, late binding does
imply dynamic dispatch, since the implementation of a late-bound
operation is not known until run time.
In practice, dynamic dispatch usually means the action of finding
the right function to call. In the general case, when you define a
method inside a class, the compiler will remember its definition
and execute it every time a call to that method is encountered.

1 #include <iostream>
2
3 class A {
4 public:
5 virtual void foo() {
6 std::cout << "This is A::foo()" << '\n';
7 }
8 };
9
10 class B : public A {
11 public:
12 void foo() override {
13 std::cout << "This is B::foo()" << '\n';
14 }
15 }
16
Object oriented design, inheritance, polymorphism 182

17 int main() {
18 A* a = new B();
19 a->foo();
20 }

If we used static dispatch the call a->foo() would execute A::foo(),


since (from the point of view of the compiler) a points to an object of
type A. This would be wrong because a actually points to an object
of type B and B::bar() should be called instead.
It’s dynamic dispatching that allows us to have this behaviour.
References:

• Pablo Arias’ blog¹⁹⁴


• Wikipedia¹⁹⁵

Question 93: What are vtable and


vpointer?
Before we continue on discovering how object sizes are calculated
we should understand what vtableandvpointer are.
Virtual tables and pointers are means to implement dynamic dis-
patch. For every class that contains virtual functions, the compiler
constructs a virtual table (vtable) serving as a lookup table of
functions used to resolve function calls in a dynamic binding
manner. The vtable contains an entry for each virtual function
accessible by the class and stores a pointer to its definition. Only
the most specific function definition callable by the class is stored in
the vtable. Entries in the vtable can point either to virtual functions
declared in the class itself or to virtual functions inherited from a
base class.
¹⁹⁴https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/
¹⁹⁵https://en.wikipedia.org/wiki/Dynamic_dispatch
Object oriented design, inheritance, polymorphism 183

A vtable containing function pointers are maintained on a class


level, i.e, there is one vtable defined per class.
Every time the compiler creates a vtable for a class, it adds an extra
argument to it: a pointer to the corresponding virtual table called
the pointer. The pointer is just another class member added by the
compiler. It increases the size of every object - where the class has
a vtable - by the size of vpointer. This means that while there is one
vtable per class, there is one vpointer per object.

When a virtual function is called, the vpointer of the object is used


to find the corresponding vtable of the class. Next, the function
name is used as an index to the vtable to find the most specific
function to be executed.
References:

• Pablo Arias’ blog¹⁹⁶


• LearnCpp¹⁹⁷

Question 94: Should base class


destructors be virtual?
In the last couple of days, we’ve been talking about polymorphism,
inheritance and whatever is implied. It’s not a big surprise that we
are going to discuss destructors once again.
While the usual answer is “yes, of course”, it’s not the correct
answer. If you just think about the standard library itself, many
classes don’t have a virtual destructor. That’s something I wrote
about in strongly-types containers¹⁹⁸. So if for example std::vector
doesn’t have a virtual destructor, the answer “of course”, cannot be
correct.
¹⁹⁶https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/
¹⁹⁷https://www.learncpp.com/cpp-tutorial/125-the-virtual-table/
¹⁹⁸https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
Object oriented design, inheritance, polymorphism 184

According to Herb Sutter¹⁹⁹, “a base class destructor should be


either public and virtual or protected and nonvirtual.”
Any operation that will be performed through the base class
interface, and that should behave virtually, should be virtual. If
deletion, therefore, can be performed polymorphically through the
base class interface, then it must behave virtually and must be
virtual. The language requires it - if you delete polymorphically
without a virtual destructor, you have to face something that is
called undefined behaviour. Something that we always want to
avoid.

1 class Base { /*...*/ };


2
3 class Derived : public Base { /*...*/ };
4
5 int main() {
6 Base* b = new Derived;
7 delete b; // Base::~Base() had better be virtual!
8 }

But base classes need not always allow polymorphic deletion. In


such cases, make base class destructor protected and non-virtual.
It’ll make deletes through base class pointers fail:

1 class Base {
2 /*...*/
3 protected:
4 ~Base() {}
5 };
6
7 class Derived : public Base { /*...*/ };
8
9 int main() {
¹⁹⁹https://herbsutter.com/
Object oriented design, inheritance, polymorphism 185

10 Base* b = new Derived;


11 delete b; // error, illegal
12 }
13 /*
14 main.cpp: In function 'int main()':
15 main.cpp:11:10: error: 'Base::~Base()' is protected withi\
16 n this context
17 11 | delete b; // error, illegal
18 | ^
19 main.cpp:4:3: note: declared protected here
20 4 | ~Base() {}
21 | ^
22 */

References:

• GotW.ca: Herb Sutter²⁰⁰


• Sandor Dargo’s Blog²⁰¹

Question 95: What is an abstract


class in C++?
An abstract class in C++ is a class with at least one pure virtual
function. A pure virtual is a function that has no implementation
in that given class.

²⁰⁰http://www.gotw.ca/publications/mill18.htm
²⁰¹https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
Object oriented design, inheritance, polymorphism 186

1 class AbstractClass {
2 public:
3 virtual void doIt() = 0;
4 };

Such classes cannot be instantiated. If you tried to declare a variable


a of AbstractClass, you’d receive an error message similar to
“cannot declare variable ‘a’ to be of abstract type ‘AbstractClass’
because the following virtual functions are pure within ‘Abstract-
Class’: virtual void AbstractClass::doIt()”
Any class that is meant to be instantiated and inherits from an
abstract one, it must implement all the pure virtual - in other words,
abstract - functions. Though non-leaf classes inheriting from an
abstract class that are only used as base classes of other classes,
they don’t have to implement the pure virtual functions:

1 class AbstractClass {
2 public:
3 virtual void doIt() = 0;
4 };
5
6 class StillAbstractClass : public AbstractClass {
7 public:
8 // no need to implement doIt, we can even add more pure\
9 virtuals
10 virtual void foo() = 0;
11 };
12
13 class Leaf : public StillAbstractClass {
14 void doIt() override { /* ... */ }
15 void foo() override {/* ... */ }
16 };

Though we cannot instantiate an abstract class, we can have


pointers or references to it:
Object oriented design, inheritance, polymorphism 187

1 int main() {
2 StillAbstractClass* s = new Leaf();
3 }

A class is abstract if it has at least one pure virtual function. So


an abstract class can have non-virtual or just non-pure virtual
functions. It can even have a constructor and some members.

Question 96: Is it possible to have


polymorphic behaviour without the
cost of virtual functions?
The short answer is yes.
How?
That’s an exciting question! By inheriting from a class that takes the
inheriting class as a template parameter. It’s called the “Curiously
Recurring Template Pattern” or “Upside Down Inheritance”. When
in Microsoft Christian Beaumont saw it in a code review, he
thought it cannot possibly compile, but it worked and it became
widely used in some Windows libraries.
Its general form is:

1 // The Curiously Recurring Template Pattern (CRTP)


2 template <class T>
3 class Base {
4 // methods within Base can use template to access membe\
5 rs of Derived
6 };
7
8 class Derived : public Base<Derived> {
9 // ...};\n
Object oriented design, inheritance, polymorphism 188

The purpose of doing this is to use the derived class in the base class.
From the perspective of the base object, the derived object is itself
but downcasted. Therefore the base class can access the derived
class by static_casting itself into the derived class.

1 template <typename T>


2 class Base {
3 public:
4 void doSomething() {
5 T& derived = static_cast<T&>(*this);
6 //use derived...
7 }
8 };

Note that contrary to typical casts to the derived class, we don’t


use dynamic_cast here. A dynamic_cast is used when you want to
make sure at run-time that the derived class you are casting into
is the correct one. But here we don’t need this guarantee: the Base
class is designed to be inherited from by its template parameter,
and by nothing else. Therefore it takes this as an assumption, and
a static_cast is enough.
References:

• Fluent C++²⁰²
• Wikipedia²⁰³
²⁰²https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/
²⁰³https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern#Static_
polymorphism
Object oriented design, inheritance, polymorphism 189

Question 97: How would you add


functionality to your classes with
the Curiously Recurring Template
Pattern (CRTP)?
The CRTP consists of:

• Inheriting from a template class


• Use the derived class itself as a template parameter of the base
class

As mentioned yesterday, the main advantage of this technique is


that the base class have access to the derived class methods. Why
is that?
The base class uses the derived class as a template parameter.
In the base class, we can get the underlying Derived object with a
static cast:

1 class Base {
2 void foo() {
3 X& underlying = static_cast<X&>(*this);
4 // now you can access X's public interface
5 }
6 };

In practice, this brings us the possibility of enriching our Derived


class’ interface through some base classes. In practice, you’d use
this technique to add some general functionalities to a class such as
some mathematical functions to a sensor class (such as explained
by Johnathan Baccara²⁰⁴). Although these functionalities can be
²⁰⁴https://www.fluentcpp.com/2017/05/16/what-the-crtp-brings-to-code/
Object oriented design, inheritance, polymorphism 190

implemented as non-member functions or non-member template


functions, those are hard to know about when you check the
interface of a class. Whereas the public methods of a CRTP’s base
class are part of the interface.
Here is the full example:

1 template <typename T>


2 struct NumericalFunctions {
3 void scale(double multiplicator);
4 void square();
5 void setToOpposite();
6 };
7
8 class Sensitivity : public NumericalFunctions<Sensitivity\
9 > {
10 public:
11 double getValue() const;
12 void setValue(double value);
13
14 // rest of the sensitivity's rich interface...
15 };
16
17 template <typename T>
18 struct NumericalFunctions {
19 void scale(double multiplicator) {
20 T& underlying = static_cast<T&>(*this);
21 underlying.setValue(underlying.getValue() * multiplic\
22 ator);
23 }
24
25 void square() {
26 T& underlying = static_cast<T&>(*this);
27 underlying.setValue(underlying.getValue() * underlyin\
28 g.getValue());
29 }
Object oriented design, inheritance, polymorphism 191

30
31 void setToOpposite() {
32 scale(-1);
33 }
34 };

You can check the below links for additional practical uses.
Resources:

• Fluent C++²⁰⁵
• Sandor Dargo’s blog²⁰⁶

Question 98: What are the good


reasons to use init() functions to
initialize an object?
It’s a short list. There is none.
When an object is created, the user can rightly expect to have a fully
usable, fully initialized object. If the user still has to call init(),
that’s not the case.
On the contrary, no destroy(), close() or alike function should
be called by the user when he stops using the object. An object
should take care of releasing whatever resources it acquired during
its initialization. This concept is also known as RAII (Resource
Acquisition Is Initialization).
In case, an object really cannot be instantiated through a construc-
tor in a convenient way, then the construction and initialization
²⁰⁵https://www.fluentcpp.com/2017/05/16/what-the-crtp-brings-to-code/
²⁰⁶https://www.sandordargo.com/blog/2019/03/13/the-curiously-recurring-templatep-
pattern-CRTP
Object oriented design, inheritance, polymorphism 192

should be encapsulated by a factory function so that the user still


doesn’t have to take care of this process.
To conclude, don’t use init() functions. An object should take care
of itself through its constructor and destructor. If the construction
is not possible like that, encapsulate it by a factory function.
References:

• Core Guidelines: C41²⁰⁷


• Core Guidelines: F50²⁰⁸

²⁰⁷https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c41-a-constructor-
should-create-a-fully-initialized-object
²⁰⁸https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c50-use-a-factory-
function-if-you-need-virtual-behavior-during-initialization
Observable behaviours
In the next couple of questions, we’ll discuss the different ob-
servable behaviours of a C++ program, such as unspecified and
undefined behaviour, etc.

Question 99: What is observable


behaviour of code?
The term observable behavior, according to the standard, means the
following:

— Accesses (reads and writes) to volatile objects occur


strictly according to the semantics of the expressions in
which they occur. In particular, they are not reordered
with respect to other volatile accesses on the same
thread.
— At program termination, all data written into files
shall be identical to one of the possible results that
execution of the program according to the abstract
semantics would have produced.
— The input and output dynamics of interactive de-
vices shall take place in such a fashion that prompting
output is actually delivered before a program waits
for input. What constitutes an interactive device is
implementation-defined.

The “as-if rule” is strongly related, in short, any code transforma-


tion is allowed that does not change the observable behavior of the
program.
Observable behaviours 194

The C++ standard precisely defines the observable behavior of


every C++ program that does not fall into one of the following
classes:

• ill-formed
• ill-formed, no diagnostic required
• implementation-defined behaviour
• unspecified behaviour
• undefined behaviour

References:

• C++ Reference: The as-if rule²⁰⁹


• C++ Reference: Undefined behaviour²¹⁰
• Stackoverflow: “Observable behaviour” and compiler free-
dom to eliminate/transform pieces c++ code²¹¹

Question 100: What are the


characteristics of an ill-formed C++
program?
Yesterday we saw that the C++ standard defines the behaviour
of C++ programs. In case the program is not well-formed, the
behaviour falls into one of the five other cases. One of them is “ill-
formed” and another is “ill-formed, no diagnostic required”
²⁰⁹https://en.cppreference.com/w/cpp/language/as_if
²¹⁰https://en.cppreference.com/w/cpp/language/ub
²¹¹https://stackoverflow.com/questions/6664471/observable-behaviour-and-compiler-
freedom-to-eliminate-transform-pieces-c-co
Observable behaviours 195

ill-formed
In this case, the program has syntax errors and/or diagnosable
semantic errors. The compiler will tell you about them. The violated
rules are written in the standard with either shall, shall not or
ill-formed.

ill-formed, no diagnostic required


Under this category, there will be no compiler errors. The program
doesn’t have syntactic errors, only semantic ones, but in general,
they are not diagnosable by the compiler.
These semantic errors are either detectable at link time, or if the
program is executed, it results in undefined behaviour.
Such problems are violations against the One Definition Rule which
says that only one definition of any variable, function, class type,
enumeration type, concept (since C++20) or template is allowed in
any one translation unit. There might be multiple declarations, but
only one definition. Check out the references for more details on
ODR.
References:

• C++ Reference: ODR²¹²


• C++ Reference: Undefined Behaviour²¹³

Question 101: What is unspecified


behaviour?
The behaviour of a program is said to be unspecified when the
standard does not specify what should happen or it specifies mul-
²¹²https://en.cppreference.com/w/cpp/language/definition
²¹³https://en.cppreference.com/w/cpp/language/ub
Observable behaviours 196

tiple options. The exact behaviour depends on the implementation


and may not be completely determined upon examination of the
program’s source code.
Even with the implementation, the compiler doesn’t have to docu-
ment how it resolves such situations.
This means that different result sets can be completely valid de-
pending on which implementation you are using.
But whatever will happen it’s guaranteed that the effects of the
unspecified code are strictly limited to the commands affected. It’s
not the case for undefined behaviour when the compiler is free to
remove complete execution branches.
Let’s see two examples of unspecified behaviour.

1 int x;
2 int y;
3 bool isHigher = &x > &y;

In other words, if you take two local variables and for any reason,
you want to compare their memory addresses, it’s completely
unspecified whose address will be higher. There is no right or good
answer, it depends on the implementation, but it doesn’t have to
document it.
The other example is related to expression evaluation orders. Take
the following piece of code
Observable behaviours 197

1 #include <iostream>
2
3 int x = 333;
4
5 int add(int i, int j) {
6 return i + j;
7 }
8
9 int left() {
10 x = 100;
11 return x;
12 }
13
14 int right() {
15 ++x;
16 return x;
17 }
18
19 int main() {
20 std::cout << add(left(), right()) << std::endl;
21 }

In this example, you call an adder function with two parameters,


both are the results of function calls on the same line. Their
evaluation order is not specified by the standard and as the invoked
functions operate on a global variable, the result depends on the
evaluation order.
Different compilers might give you different results.
I recommend using Wandbox²¹⁴ if you quickly want to run your
code with different compilers and versions.
By the way, in the recent standards, a lot of similar cases have been
specified. Check out the references for more details.
References:
²¹⁴https://wandbox.org/
Observable behaviours 198

• Wikipedia²¹⁵
• GeeksForGeeks²¹⁶
• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea
2020)²¹⁷

Question 102: What is


implementation-defined behaviour?
In many ways, implementation-defined behaviour is similar to
unspecified behaviour. First of all, the standard doesn’t impose on
how the concerned item should be implemented. That will depend
on the compiler.
It’s also true that the effects of implementation-defined behaviour
are limited to the very commands in question. So no full execution
branches can be removed, like in the case of undefined behaviour.
What differs between unspecified and implementation-defined be-
haviour is that the implementation must document what is a valid
result set.
Let’s see some examples of implementation-defined behaviour and
let’s step out of the C++ world for a moment.
If you think about SQL and how ORDER BY works on NULL values
you’ll find that it differs between the different implementations.
Some will put NULL at the beginning of an ordered result set, some
will put such values in the back. You don’t have to figure this out
by trying, you’ll find it in the documentation, it’s implementation-
defined behaviour.
In C++, sizeof(int) and the size of integer types, in general, is
implementation specified. And even the size of a byte.
²¹⁵https://en.wikipedia.org/wiki/Unspecified_behavior
²¹⁶https://www.geeksforgeeks.org/unspecified-behavior-in-c-c-with-examples/
²¹⁷https://www.youtube.com/watch?v=BEmAo6Fdg-Q
Observable behaviours 199

References:

• Quora: What is the difference between undefined, unspecified


and implementation-defined behavior?²¹⁸
• StakcOverFlow: Undefined, unspecified and implementation-
defined behavior²¹⁹
• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea
2020)²²⁰

Question 103: What is undefined


behaviour in C++?
Among unspecified, implementation-defined and undefined be-
haviour, this latter one is the most dangerous.
When you implement a program invoking some behaviour that is
undefined, it basically means that there are no requirements on
the behaviour of your whole program. The possible effects are not
limited to the calls whose behaviour are unspecified, the compiler
can do anything so your software can:

• crash with seemingly no reasons


• return logically impossible results
• have non-deterministic behaviour

In fact, the compiler can remove whole execution paths. Let’s


say you have a big function with one line invoking undefined
behaviour. It’s possible that the whole function will be removed
from the compiled code.
²¹⁸https://www.quora.com/What-is-the-difference-between-undefined-unspecified-and-
implementation-defined-behavior
²¹⁹https://stackoverflow.com/questions/2397984/undefined-unspecified-and-
implementation-defined-behavior
²²⁰https://www.youtube.com/watch?v=BEmAo6Fdg-Q
Observable behaviours 200

When you have undefined behaviour in your code, you break


the rules of the language and it won’t be detected until runtime.
Turning on as many compiler warnings as possible usually helps a
lot in removing undefined behaviour.
Some examples of undefined behaviour:

• Accessing uninitialized variables


• Accessing objects after the lifetime ended
• Deleting objects through base class pointers without a virtual
destructor

Avoid undefined behaviour like the plague.


References:

• Quora: What is the difference between undefined, unspecified


and implementation-defined behavior?²²¹
• StakcOverFlow: Undefined, unspecified and implementation-
defined behavior²²²
• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea
2020)²²³

Question 104: What are the reasons


behind undefined behaviour’s
existence?
First of all, we have to remark that the concept of undefined
behaviour was not introduced by C++. It was already there in C.
²²¹https://www.quora.com/What-is-the-difference-between-undefined-unspecified-and-
implementation-defined-behavior
²²²https://stackoverflow.com/questions/2397984/undefined-unspecified-and-
implementation-defined-behavior
²²³https://www.youtube.com/watch?v=BEmAo6Fdg-Q
Observable behaviours 201

And what is C after all? It’s just a high-level assembler that had to
work on completely different platforms and architectures.
From the language designer’s point of view, undefined behaviour
is a way to cope with significant differences between compilers
and between platforms. Some even refer to that epoch as chaotic.
Different compilers treated the language differently and to be fairly
backwards-compatible, a lot of details (like layout, endianness)
were not defined or specified.
This gave compiler writers a lot of flexibility and they could and
still can get really creative with this freedom. They can use it
to simplify, to shorten, to speed up the compiled code without
violating any rules.
References:

• StackExchange: Why does C++ have ‘undefined behaviour’


(UB) and other languages like C# or Java don’t?²²⁴
• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea
2020)²²⁵

Question 105: What approaches to


take to avoid undefined behaviour?
Maybe first we can discuss what will not work.
Using try-catch blocks will not going to work, undefined behaviour
is not about exceptions handled in the wrong way. Similarly, some
explicit checks for example on input containers are going to work
either.

²²⁴https://softwareengineering.stackexchange.com/questions/398703/why-does-c-have-
undefined-behaviour-ub-and-other-languages-like-c-or-java
²²⁵https://www.youtube.com/watch?v=BEmAo6Fdg-Q
Observable behaviours 202

1 std::copy_if(numbers21.begin(), numbers22.end(),
2 std::back_inserter(copiedNumbers),
3 [](auto number) {return number % 2 == 1;});

There is no valid check to avoid such a typo, but it’s true that with
some defensive programming you could handle the situation.

1 std::vector<int> doCopy(std::vector<int> numbers) {


2 std::vector<int> copiedNumbers;
3 std::copy_if(numbers.begin(), numbers.end(),
4 std::back_inserter(copiedNumbers),
5 [](auto number) {return number % 2 == 1;});
6 return copiedNumbers;
7 }
8
9 // ...
10 auto copiedNumbers = doCopy(numbers);

This is just a simple example, but the bottom line is that by


limiting the scope of a function, by limiting the number of available
variables, you can limit the compilable typos you can make. Yet, it
doesn’t look nice to wrap every algorithm like that.
So what can you do against undefined behaviour?
- You can listen to your compiler! Turn on whatever warnings you
can (-Wall, -Wextra, -Wpedantic) and treat them as errors. They will
catch a lot of undefined behaviour.

• Use a sanitizer, both g++ and clang offer some


• Follow coding best practices and naming guidelines. In the
above example, the typo was only possible due to low-quality
names (numbers21 and number22). Legacy code have a lot of
such names. Use proper, descriptive names and you won’t
make such typos.
Observable behaviours 203

• Understand the concepts behind the language. If you consider


that in C++ you shouldn’t pay for things you don’t use,
it becomes self-evident why std::binary_search expects a
sorted input range.
• Practice contractual programming. If you want to use ele-
ments of the standard library (you should), check the contract
it proposes, in other words, read their documentation, read
what kind of input they expect.
• Share the knowledge! If you learn something, share it with
your teammates. Organize dedicated sessions, use the team
chat and code reviews.

Question 106: What is iterator


invalidation? Give a few examples.
When you declare an iterator pointing at an element of a container,
you might expect it to be valid all the time. There are some iterators
that will be always valid and point at the place you’d expect it to
point to.
For example, insert iterators (e.g. std::back_insert_iterator)
are guaranteed to remain valid as long as all insertions are
performed through this iterator and no other independent
iterator-invalidating event occurs. They will even remain valid if a
container has to be reallocated as it grows (notably a std::vector).
Also, read-only methods never invalidate iterators or references.
Methods that modify the contents of a container may invalidate
iterators and/or references.
Refer to this table²²⁶ to have a full list of when a container is
invalidated.
Here is an example:
²²⁶https://en.cppreference.com/w/cpp/container#Iterator_invalidation
Observable behaviours 204

1 #include <algorithm>
2 #include <iostream>
3 #include <vector>
4
5 int main() {
6 std::vector<int> numbers { 1, 2, 3, 4, 5, 6, 4};
7
8 int val = 4;
9 std::vector<int>::iterator it;
10 for (it = numbers.begin(); it != numbers.end(); ++it) {
11 if (*it == val) {
12 numbers.erase(it);
13 numbers.shrink_to_fit();
14 }
15 }
16
17 std::cout << "numbers after erase:";
18 for (const auto num : numbers) {
19 std::cout << num << " ";
20 }
21 }

When erase is called with it, its position became invalidated and
what should happen in the next iteration is undefined behaviour.
You might find that everything is fine, or that the results are not
coherent, or event you can get a segmentation fault. Compiler the
above code and check it yourself, play with the inputs.
References:

• C++ Reference: Iterator invalidation²²⁷


• Stackoverflow: Iterator invalidation rules²²⁸
• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea
2020)²²⁹
²²⁷https://en.cppreference.com/w/cpp/container#Iterator_invalidation
²²⁸https://stackoverflow.com/questions/6438086/iterator-invalidation-rules
²²⁹https://www.youtube.com/watch?v=BEmAo6Fdg-Q
The Standard Template
Library
During a couple of questions, we’ll learn about the Standard
Template Library, its history, concepts and some algorithms.

Question 107: What is the STL?


STL stands for the Standard Template Library, but there is no such
library as the STL. It is part of the C++ standard library.
It’s a set of template classes and functions to provide solutions for
common problems. The elements of the STL can be divided into 4
categories:

• algorithms, e.g.: std::find, std::copy


• containers, e.g.: std::vector<T>, std::list<T>
• function objects, e.g.: std::greater<T>, std::logical_and<T>
• iterators, e.g.: std::iterator, std::back_inserter

The STL was created by Alexander Stepanov, who already had the
idea of a generic data processing library in the 1970s, but there was
no language support to implement his dream.
In the 80s, he made his first attempts in ADA, but that language
never got widely adopted outside the defence industry.
A former colleague convinced him to present the idea to the C++
Committee that he did in November 1993. Then things happened
fast. In March 1994, Stepanov submitted the formal proposal which
was accepted in just a mere 4 months. In August, HP - the employer
of Stepanov - published the first implementation of the STL.
The Standard Template Library 206

Question 108: What are the


advantages of algorithms over raw
loops?
First, what are raw loops?
“A raw loop is any loop inside a function where the function serves
a purpose larger than the algorithm. implemented by the loop.” -
Sean Parent, C++ Seasoning²³⁰.

Why should you prefer using std algorithms


instead of such raw loops?

Algorithms are virtually bug-free

If you have to write something a thousand times, there is a fair


chance that you’ll make some mistakes once in a while. It’s OK, we
all make mistakes. On the other hand, if you use functions that were
written before and used a million times, you won’t face any bugs.

Algorithms have a better performance

This is only partially true. If we speak about C++, functions


in the <algorithms></algorithms> header are not optimized for
corner cases. They are optimized for certain portability between
different systems and container types. You can use them on any STL
container without knowing their exact type. As such, we cannot
assume that they can take advantage of the characteristics of the
underlying datasets. Especially that they don’t operate directly on
the containers, but through the iterators that give access to data
behind. I say that we cannot assume, because in fact, very few
people understand what is going on under the hoods of the compiler
²³⁰https://channel9.msdn.com/Events/GoingNative/2013/Cpp-Seasoning
The Standard Template Library 207

and you might find or write an implementation of the standard


library that is much bigger than the usual ones, but optimized for
each container type.
At the same time, chances are good that your for loops are not
optimized either. And it’s alright. Of course, as you write your
loops, you are in control. You can optimize them, you can get the
last cycles out of them. You cannot do the same with the already
written functions of a library, even if it’s the standard library.
But what reasonably could be done, was already done for the
standard algorithms, and most of those things are are not performed
for the raw loops we so often used. In the end, algorithms have
better performance.

Algorithms are more expressive

Raw loops contain low-level code. When you call algorithms, those
calls expressive.\nLet’s take a simple example.\nWhat does the
following piece of code do?

1 const std::vector<int> v{1, 2, 3, 4, 5};


2 auto ans = false;
3 for (const auto& e : v) {
4 if (e % 2 == 0) {
5 ans = true;
6 break;
7 }
8 }

After some thinking, we can say that it tells you if there is any even
element in the vector v.\nHow could we make it more readable?
With an algorithm:
The Standard Template Library 208

1 const std::vector<int> v{1, 2, 3, 4, 5};


2
3 const auto ans = std::any_of(v.begin(), v.end(),
4 [](const auto& e) {return e \
5 % 2 ==0;});

How much easier is that?\nAnd this was only one simple example.

Conclusion

Algorithms are most of the time better than plain old for loops.
They are less error-prone than loops as they were already written
and tested - a lot. Unless you are going for the last drops of
performance, algorithms will provide be good enough for you and
actually more performant than simple loops.
But the most important point is that they are more expressive. It’s
straightforward to pick the good among many, but with education
and practice, you’ll be able to easily find an algorithm that can
replace a for loop in most cases.

Question 109: Do algorithms


validate ranges?
Would the following piece of code compile? What do you expect the
content of copiedNumbers to be?
The Standard Template Library 209

1 auto numbers21 = { 1, 3 }
2 auto numbers22 = { 3, 5 };
3
4 std::vector<int> copiedNumbers;
5
6 std::copy_if(numbers21.begin(), numbers22.end(),
7 std::back_inserter(copiedNumbers),
8 [](auto number) {return number % 2 == 1;});

std::copy_if takes three parameters, the start and the end iterator
of the range to be copied if the third parameter (a function pointer,
a function object or a lambda expression) evaluates to true.
std::copy_if - or any function from the <algorithm> header by
the way - don’t, it cannot evaluate whether the start and the end
iterator, its first two parameters, belong to the same container or
not. It’s the responsibility of the caller to make sure that the correct
parameters are passed in. If they are not respecting the rules, the
result is the dreaded undefined behaviour.
So what will happen is that inside the copy_if, the iterator pointing
at the current position will be incremented as long as it doesn’t
reach the end iterator. What if the address of the end is actually
before the starting point? What if the two containers are next to
each other? What if there is some garbage between them?
It’s all undefined behaviour. You might have seemingly correct
results, like the combination of the two containers, you might get
a timeout or a nice core dump.
The only thing the compiler can validate is that two iterators are
of the same type. So you cannot combine a list with a vector or a
vector of ints with a vector of floats.

You have to always double-check that what you pass in is valid and
respect the contracts imposed by the given algorithm.
The Standard Template Library 210

Question 110: Can you combine


containers of different sizes?
Explain what the following piece of code does!

1 #include <algorithm>
2 #include <iostream>
3 #include <vector>
4
5 int main() {
6 std::vector<int> values{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 \
7 };
8 std::vector<int> otherValues{ 10, 20, 30 };
9 std::vector<int> results;
10
11 std::transform(values.begin(), values.end(),
12 otherValues.begin(),
13 std::back_inserter(results),
14 [](int number, int otherNumber) {
15 return number + otherNumber;
16 });
17
18 std::cout << "copied numbers: ";
19 for (const auto& number : results) {
20 std::cout << ' ' << number;
21 }
22 std::cout << '\n';
23
24 return 0;
25 }

First, what can be the intention behind such code? With


std::transform we try to combine the content of two vectors, by
adding up the nth element of the values with the nth element of
the otherValues and we push the sum to the results vector.
The Standard Template Library 211

It’s simple code, but there is a catch. The two ranges that are passed
in, don’t have the same amount of elements.
Remember, std::transform takes the first range by a begin and an
end iterator and a second input range (which is not mandatory by
the way) only by the begin iterator.
Like all the similar functions in the <algorithm> header,
std::transform assumes and in fact, expects that the second
input range has at least as many elements as the first.
But what if this expectation is not met?
You might expect that a zero-initialized item will be taken instead,
but it’s not the case, even though it’s easy to come up with some
example code that would support this assumption, but no.
It’s undefined behaviour.
In most cases, the runtime will just take any value that it finds in
the next memory address even if that does not belong to the vector.
So you always have to make sure that the second input range,
defined only by its starting point is always at least as long as the
first one.

Question 111: How is a vector’s


memory layout organized?
If you create a vector locally on the stack (without using new) you
might expect that you will have the data on the stack. But in fact,
the vector object on the stack itself is quite small and it consists of
a pointer that point at some dynamically allocated memory on the
heap.
So on the stack, you’ll have the above-mentioned pointer and some
other extra variables to keep track of the size and the capacity. You
The Standard Template Library 212

can take a look at this illustration²³¹.


Other implementations are also possible when iterators are stored
pointing at the beginning of the allocated memory, at the current
end and at the end of the total capacity. Check this illustration²³².
The bottom line is that you have some variables in the main vector
object that help to find the data that is actually stored on the heap.
That memory on the heap must be contiguous and if your vector
grows and an extension is required it might happen that the whole
content must be copied to somewhere else.
In order to protect yourself from these extra copies, if you
know how big your vector will grow, it’s good to reserve
that capacity right away after its declaration, by calling
std::vector<T>::reserve(maxCapacity)

References:

• Frogatto: How C++’s vector works: the gritty details²³³


• STHU.org: Array-like C++ containers: Four steps of trading
speed²³⁴

Question 112: Can we inherit from a


standard container (such as
std::vector)? If so what are the
implications?
The standard containers declare their constructors as public and
non-final, so yes it is possible to inherit from them. In fact, it’s
a well-known and used technique to benefit from strongly typed
containers.
²³¹https://www.sthu.org/blog/16-cpp-arrays/img/std_vector_layout.svg
²³²https://frogatto.com/wp-content/uploads/2009/11/vector.png
²³³https://frogatto.com/2009/11/17/how-cs-vector-works-the-gritty-details/
²³⁴https://www.sthu.org/blog/16-cpp-arrays/index.html
The Standard Template Library 213

1 class Squad : public std::vector {


2 using std::vector::vector;
3 // ...
4 };

It’s simple, it’s readable, yet you’ll find a lot of people at different
forums who will tell you that this is the eighth deadly sin and if
you are a serious developer you should avoid it at all costs.
Why do they say so?
There are two main arguments. One is that algorithms and contain-
ers are well-separated concerns in the STL. The other one is about
the lack of virtual constructors.
But are these valid concerns?
They might be. It depends.
Let’s start with the one about the lack of a virtual destructor. It
seems more practical.
Indeed, the lack of a virtual destructor might lead to undefined
behaviour and a memory leak. Both can be serious issues, but the
undefined behaviour is worse because it can not just lead to crashes
but even to difficult to detect memory corruption eventually lead-
ing to strange application behaviour.
But the lack of virtual destructor doesn’t lead to undefined be-
haviour and memory leak by default, you have to use your derived
class in such a way.
If you delete an object through a pointer to a base class that has
a non-virtual destructor, you have to face the consequences of
undefined behaviour. Plus if the derived object introduces new
member variables, you’ll also have some nice memory leak. But
again, that’s the smaller problem.
On the other hand, this also means that those who rigidly oppose
inheriting from std::vector - or from any class without a virtual
The Standard Template Library 214

destructor - because of undefined behaviour and memory leaks, are


not right.
If you know what you are doing, and you only use this inheritance
to introduce a strongly typed vector, not to introduce polymorphic
behaviour and additional states to your container, you are perfectly
fine to use this technique. Simply, you have to respect the limita-
tions, though probably this is not the best strategy to use in the case
of a public library. But more on that just in a second.
So the other main concern is that you might mix containers and
algorithms in your new object. And it’s bad because the creators
of the STL said so. And so what? Alexander Stepanov²³⁵ who
originally designed the STL and the others who have been later
contributed to it are smart people and there is a fair chance that they
are better programmers than most of us. They designed functions,
objects that are widely used in the C++ community. I think it’s okay
to say that they are used by everyone.
Most probably we are not working under such constraints, we are
not preparing something for the whole C++ community. We are
working on specific applications with very strict constraints. Our
code will not be reused as such. Never. We don’t work on generic
libraries, we work on one-off business applications.
As long as we keep our code clean (whatever it means), it’s perfectly
fine to provide a non-generic solution.
As conclusion, we can say that for application usage, inheriting
from containers in order to provide strong typing is fine, as long
as you don’t start to play with polymorphism.
²³⁵https://en.wikipedia.org/wiki/Alexander_Stepanov
The Standard Template Library 215

Question 113: What is the type of


myCollection after the following
declaration?
1 auto myCollection = {1,2,3};

The type is std::initializer_list. The int part is probably


straightforward, and about std::initializer_list, well you just
have to know, that with auto type deduction, if you use braces,
you have two options.
If you put nothing between the curly braces, you’ll get
a compilation error as the compiler is unable to deduce
‘std::initializer_list<auto>’ from ‘<brace-enclosed initializer=””
list=””>()’</brace-enclosed></auto>. So no empty container, but a
compilation error.
If you have at least one element between the braces, the type will
be std::initializer_list.
If you wonder what this type is, you should know that it is a
lightweight proxy object providing access to an array of objects
of type const T. It is automatically constructed when:

• a braced-init-list is used to list-initialize an object, where


the corresponding constructor accepts an std::initializer_list
parameter
• a braced-init-list is used on the right side of an assign-
ment or as a function call argument, and the corresponding
assignment operator/function accepts an std::initializer_list
parameter
• a braced-init-list is bound to auto, including in a ranged for
loop
The Standard Template Library 216

Initializer lists may be implemented as a pair of pointers or pointer


and length. Copying a std::initializer_list is considered a
shallow copy as it doesn’t copy the underlying objects.

Question 114: What are the


advantages of const_iterators over
iterators?
Iterators are like pointers to const objects but in the STL world.
They point to values that cannot be modified. The usage of const_-
iterators simply follows the same principles as using const vari-
ables wherever it makes sense.
Since C++11 all STL containers have cbegin and cend member
functions producing const_iterators even if the container itself
is not const.
Since C++14, you also have cbegin, cend, crbegin, crend non-
member functions available. It’s better to use the non-member
functions in case you want generic code, because these will also
work on built-in arrays, not like the member functions.

Question 115: Binary search an


element with algorithms!
What do you expect that this piece of code does? Will it find 7?
The Standard Template Library 217

1 #include <algorithm>
2 #include <iostream>
3 #include <vector>
4
5 int main() {
6 std::vector<int> numbers{1,54,7,5335,8};
7 std::cout << std::binary_search(numbers.begin(), number\
8 s.end(), 7) << std::endl;
9 }

It will most probably not find the position of 7 and even if you
have a good result, you cannot rely on it. In fact, the above code
has undefined behaviour.
It’s all about contracts and principles. In C++, one of the foun-
dational concepts is that you should not pay for what you don’t
use. According to this spirit, std::binary_search and some other
algorithms expect that its input range is sorted. After all, search
algorithms should not be responsible for sorting and those who
already pass in a sorted range should not pay for a needless sorting
attempt.
So to make this code working, you just have to sort the vector before
passing it to the search.

1 #include <algorithm>
2 #include <iostream>
3 #include <vector>
4
5 int main() {
6 std::vector<int> numbers{1,54,7,5335,8};
7 std::sort(numbers.begin(), numbers.end());
8 std::cout << std::binary_search(numbers.begin\
9 (),
10 numbers.end(),\
11 7) << '\n';}
The Standard Template Library 218

The bottom line is that algorithms come with their contracts which
you should consult first and - of course - respect.
Reference:

• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea


2020)²³⁶

Question 116: What is an Iterator


class?
As you probably remember, the Standard Template Library is com-
posed of containers, algorithms, functors and iterators. Iterators are
the connecting dots between containers and functors.
In the STL, before ranges appeared, algorithms never operated
directly on the containers, but on iterators instead.
An iterator is like a pointer. It’s an object that, pointing to some
element in a range of elements (such as an array or a container),
has the ability to iterate through the elements of that range using
a set of operators (with at least the increment (++) and dereference
(*) operators).
Iterators have different categories and their classification depends
on the functionality they implement. The list goes from the most
generic towards the most specialized category:

• Random Access
• Bidirectional
• Forward
• Input
• Output (input and output are, in fact, on the same level)
²³⁶https://www.youtube.com/watch?v=BEmAo6Fdg-Q
The Standard Template Library 219

To get more detailed information about the categories, check out


this link²³⁷.
References:

• CPlusPlus.com: Iterator²³⁸
• Learn C++: Introduction to iterators²³⁹

²³⁷https://www.cplusplus.com/reference/iterator/
²³⁸https://www.cplusplus.com/reference/iterator/
²³⁹https://www.learncpp.com/cpp-tutorial/introduction-to-iterators/
Miscalleanous
During the rest of this book, we are going to cover various topics,
such as some tricky code problems, optimizations, and the C++ core
guidelines.

Question 117: Can you call a virtual


function from a constructor or a
destructor?
Technically you can, the code will compile. But the code can be
misleading and it can even lead to undefined behaviour.
Attempting to call a derived class function from a base class under
construction is dangerous.

1 #include <iostream>
2
3 class Base {
4 public:
5 Base() {
6 foo();
7 }
8 protected:
9 virtual void foo() {
10 std::cout << "Base::foo\n";
11 }
12 };
13
14 class Derived : public Base {
Miscalleanous 221

15 public:
16 Derived() { }
17 void foo() override {
18 std::cout << "Derived::foo\n";
19 }
20 };
21
22 int main() {
23 Derived d;
24 }

The output is simply Base::foo:

• By contract, the derived class constructor starts by calling the


base class constructor.
• The base class constructor calls the base member function and
not the one overridden in the child class, which is confusing
for the child class’ developers.

In case, the virtual method is also a pure virtual method, you have
undefined behaviour. If you’re lucky, at link time you’ll get some
errors.
What’s the solution?
The simplest probably is just to fully reference the function that
you’ll call:

1 Base() {
2 Base::foo();
3 }

In the case of the Base class, it can only be that, when you’re in
the Derived class, you can decide. A more elegant solution is if you
wrap the virtual functions in non-virtual functions and from the
constructors/destructors you only use them.
Miscalleanous 222

For full source code, check out the references.


References:

• SEI CERT: OOP50-CPP. Do not invoke virtual functions from


constructors or destructors²⁴⁰
• SonarSource: RPSEC-1699²⁴¹

Question 118: What are default


arguments? How are they evaluated
in a C++ function?
A default parameter is a value that is assigned to a parameter while
declaring a function.
A default argument allows a function to be called without providing
one or more trailing arguments.
The default value for a parameter is indicated at function decla-
ration time at the parameter list. We simply assign a value to the
parameter in the function declaration.
Here is an example:

1 int calculateArea(int a, int b);


2 int calculateArea(int a, int b=2);
3 int calculateArea(int a=3, int b=2);

These default values are used if one or more arguments are left
blank while calling the function - according to the number of left
blank arguments.
²⁴⁰https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP50-CPP.+Do+not+invoke+
virtual+functions+from+constructors+or+destructors
²⁴¹https://rules.sonarsource.com/cpp/RSPEC-1699
Miscalleanous 223

Let’s see a complete example. If the value is not passed for any of
the parameters during the function call, then the compiler uses the
default value(s) provided. If a value is specified, then the default
value is stepped on and the passed value is used.

1 #include <iostream>
2
3 int calculateArea(int a=3, int b=2) {
4 return a*b;
5 }
6
7 int main() {
8 std::cout << calculateArea(6, 4) << '\n';
9 std::cout << calculateArea(6) << '\n';
10 std::cout << calculateArea() << '\n';
11 }
12 /*
13 24
14 12
15 6
16 */

As shown in the above code, there are three calls to calculateArea


function. In the first call, we pass both arguments so the default
values are overridden by the caller, in the second only one, so the
last parameter defaults. In the last call, we provide no argument, so
both parameters take their default values.
In a function declaration, after a parameter with a default argument,
all subsequent parameters must have a default argument supplied
in this or a previous declaration from the same scope…unless the
parameter was expanded from a parameter pack, and keep in mind
that ellipsis(...) is not a parameter.
This means that that the int calculateArea(int a=5, int b);
would not compile. On the other hand, int g(int n = 0, ...)
would compile as ellipsis does not count as a parameter.
Miscalleanous 224

Default arguments are only allowed in the parameter lists of func-


tion declarations and lambda-expressions, (since C++14) and are
not allowed in the declarations of pointers to functions, references
to functions, or in typedef declarations.
One last thing to note is that you should always declare your
defaults in the header, not in the cpp file.

Question 119: Can virtual functions


have default arguments?
Yes, they can, but you should not rely on this feature, as you might
not get what you’d expect.
While it’s perfectly legal to use default argument initializers in
virtual functions, there is a fair chance over the maintenance period,
changes will lead to incorrect polymorphic code and unnecessary
complexity in a class hierarchy.
Let’s see an example:

1 #include <iostream>
2
3 class Base {
4 public:
5 virtual void fun(int p = 42) {
6 std::cout << p << std::endl;
7 }
8 };
9
10 class Derived : public Base {
11 public:
12 void fun(int p = 13) override {
13 std::cout << p << std::endl;
14 }
Miscalleanous 225

15 };
16
17 class Derived2 : public Base {
18 public:
19 void fun(int p) override {
20 std::cout << p << std::endl;
21 }
22 };

What would you expect from the following main function?

1 int main() {
2 Derived *d = new Derived;
3 Base *b = d;
4 b->fun();
5 d->fun();
6 }

If you expected, the following, congrats!

1 42
2 13

If not, don’t worry. It’s not evident. b points to a derived class, yet
B’s default value was used.

Now, what about the following possible main?


Miscalleanous 226

1 int main() {
2 Base *b2 = new Base;
3 Derived2 *d2 = new Derived2;
4 b2->fun();
5 d2->fun();
6 }

You might expect 42 twice in a row, but that’s incorrect. The code
won’t compile. The overriding function doesn’t “inherit” the default
value, so the empty fun call to Derived2 fails.

1 main.cpp: In function 'int main()':


2 main.cpp:28:10: error: no matching function for call to '\
3 Derived2::fun()'
4 28 | d2->fun();
5 | ~~~~~~~^~
6 main.cpp:19:8: note: candidate: 'virtual void Derived2::f\
7 un(int)'
8 19 | void fun(int p) override {
9 | ^~~
10 main.cpp:19:8: note: candidate expects 1 argument, 0 pr\
11 ovided

Now let’s modify a bit our original example and we’ll ignore
Derived2.
Miscalleanous 227

1 #include <iostream>
2
3 class Base {
4 public:
5 virtual void fun(int p = 42) {
6 std::cout << "Base::fun " << p << std::endl;
7 }
8 };
9
10 class Derived : public Base {
11 public:
12 void fun(int p = 13) override {
13 std::cout << "Derived::fun" << p << std::endl;
14 }
15 };
16
17 int main() {
18 Derived *d = new Derived;
19 Base *b = d;
20 b->fun();
21 d->fun();
22 }

What output do you expect now?


It is going to be:

1 Derived::fun 42
2 Derived::fun 13

The reason is that a virtual function is called on the dynamic type


of the object, while the default parameter values are based on the
static type. The dynamic type is Derived in both cases, but the static
type is different, hence the different default values are used.
Given these differences compared to normal polymorphic
Miscalleanous 228

behaviour, it’s best to avoid any default arguments in virtual


functions.
References:

• GotW.ca: Herb Sutter²⁴²


• SonarSource²⁴³

Question 120: Should base class


destructors be virtual?
In the last couple of days, we’ve been talking about polymorphism,
inheritance and whatever is implied. It’s not a big surprise that we
are going to discuss destructors once again.
While the usual answer is “yes, of course”, it’s not the correct
answer. If you just think about the standard library itself, many
classes don’t have a virtual destructor. That’s something I wrote
about in strongly-types containers²⁴⁴. So if for example std::vector
doesn’t have a virtual destructor, the answer “of course”, cannot be
correct.
According to Herb Sutter²⁴⁵, “a base class destructor should be
either public and virtual or protected and nonvirtual.”
Any operation that will be performed through the base class
interface, and that should behave virtually, should be virtual. If
deletion, therefore, can be performed polymorphically through the
base class interface, then it must behave virtually and must be
virtual. The language requires it - if you delete polymorphically
without a virtual destructor, you have to face something that is
called undefined behaviour. Something that we always want to
avoid.
²⁴²http://www.gotw.ca/publications/mill18.htm
²⁴³https://rules.sonarsource.com/cpp/RSPEC-3719
²⁴⁴https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
²⁴⁵https://herbsutter.com/
Miscalleanous 229

1 class Base { /*...*/ };


2
3 class Derived : public Base { /*...*/ };
4
5 int main() {
6 Base* b = new Derived;
7 delete b; // Base::~Base() had better be virtual!
8 }

But base classes need not always allow polymorphic deletion. In


such cases, make base class destructor protected and non-virtual.
It’ll make deletes through base class pointers fail:

1 class Base {
2 /*...*/
3 protected:
4 ~Base() {}
5 };
6
7 class Derived : public Base { /*...*/ };
8
9 int main() {
10 Base* b = new Derived;
11 delete b; // error, illegal
12 }
13 /*
14 main.cpp: In function 'int main()':
15 main.cpp:11:10: error: 'Base::~Base()' is protected withi\
16 n this context
17 11 | delete b; // error, illegal
18 | ^
19 main.cpp:4:4: note: declared protected here
20 4 | ~Base() {}
21 | ^
22
23 */
Miscalleanous 230

References:

• GotW.ca: Herb Sutter²⁴⁶


• Sandor Dargo’s Blog²⁴⁷

Question 121: What is the function


of the keyword mutable?
mutable has two functions, two roles since C++11.

mutable specifier
Let’s start with its older meaning.\nmutable permits modification
of the class members even if they are declared const.
It may appear in the declaration of a non-static class members of
non-reference non-const type:

1 class X {
2 mutable const int* p; // OK
3 mutable int* const q; // ill-formed
4 };

Mutable is used to specify that the member does not affect the
externally visible state of the class (as often used for mutexes, memo
caches, lazy evaluation, and access instrumentation).
So in case you have a const object, it’s mutable members can be
modified.
Another way to use mutable is in case of lazy initialization. Getters
should be generally const methods as they are only supposed to
return class members, and not alter them.
²⁴⁶http://www.gotw.ca/publications/mill18.htm
²⁴⁷https://www.sandordargo.com/blog/2020/10/14/strong-types-for-containers
Miscalleanous 231

But when you apply lazy initialization, the first time you call the
getter, it will actually alter the member. It will initialize it maybe
from the DB, via the network, etc. So you cannot make that member
const even if its value will not change after initialization.
If you use the mutable keyword, you can. Just pay attention not to
do other things to that member.

1 class SomethingExpensive{
2 //...
3 };
4
5 class A{
6 public:
7 SomethingExpensive* getSomethingExpensive() const {
8 if (_lazyMember == nullptr) {
9 _lazyMember = new SomethingExpensive();
10 }
11 return _lazyMember;
12 }
13
14 private:
15 mutable SomethingExpensive* _lazyMember;
16 };

Mutable lambda expressions

If you declare a lambda mutable, then it allows the lambda to


modify the objects captured by copy, and to call their non-const
member functions. Otherwise, it’s not possible.
Miscalleanous 232

1 #include <iostream>
2
3 struct A{
4 void a() const {
5 std::cout << "a\n";
6 }
7
8 void b() {
9 std::cout <<"b\n";
10 }
11 };
12
13 int main(){
14 A a;
15
16 // error: passing 'const A' as 'this'
17 // auto l = [a]() {a.b();};
18
19 auto lm = [a]() mutable {a.b();};
20 return 0;
21 }

It’s worth trying in C++ Insights²⁴⁸ the above code and try to
check what’s the difference when you declare a lambda mutable
and not. You will find that when a lambda is non-mutable, then
the operator() is const. When it’s mutable it becomes non-const.
You can also observe that the generated object takes the captured
variables as members. As such, it becomes straightforward why a
regular lambda expression cannot call non-const functions on the
captured variables.
²⁴⁸https://cppinsights.io/
Miscalleanous 233

Question 122: What is the function


of the keyword volatile?
It is a keyword to declare that the corresponding variable is
volatile and thereby tells the compiler that the variable can change
externally and as such, the compiler optimization on the variable
reference should be avoided.
What does this mean in plain English?
It means that if you declare a variable volatile, you acknowledge
that the value stored at the variable’s memory address might
change independently.
How could that happen?
It might happen because of changes introduced by hardware or by
another thread.
Speaking of this latter one, while it’s possible, according to the
C++11 ISO Standard, the volatile keyword is only meant for use
for hardware access; do not use it for inter-thread communication.
For inter-thread communication, the standard library provides
std::atomic templates.

While the original guarantees for volatile promised that the order
of assignments to volatile qualified variable is preserved, it does not
mean that no reordering will happen around volatile assignments.
So don’t use volatile for inter-thread communication, it is a type
qualifier that you can use in order to declare that an object can be
modified in the program by the hardware.
A memory model was added to the C++11 standard to support
multi-threading. But in C++ the decision was to keep the keyword
volatile as a mean to solve the original memory mapped I/O
problems.
Miscalleanous 234

Question 123: What is an inline


function?
A function prefixed with the keyword inline before the function
definition is called as inline function. If a function is inline the
compiler places a copy of the code of that function at each point
where the function is called at compile time.
Any change to an inline function could require all clients of the
function to be recompiled because the compiler would need to
replace all the code once again otherwise it will continue with old
functionality.
To inline a function, simply place the keyword inline before the
function name and define the function before any calls are made to
the function. It’s important to remember that it’s often only a hint
to the compiler. The compiler can ignore the inline qualifier in case
the defined function is more than a line.
A function definition in a class definition is an inline function
definition, even without the use of the inline specifier.
To understand how inline functions can help, we should under-
stand the costs of a regular function call.
When the program executes the function call instruction the CPU
stores the memory address of the instruction following the function
call, copies the arguments of the function on the stack and finally
transfers control to the specified function. The CPU then executes
the function code, stores the function return value in a predefined
memory location/register and returns control to the calling func-
tion.
This can become overhead if the execution time of a function is less
than the switching time from the caller function to called function.
For functions that are large and/or perform complex tasks, the
overhead of the function call is usually insignificant compared to
Miscalleanous 235

the amount of time the function takes to run. However, for small,
commonly-used functions, the time needed to make the function
call is often a lot more than the time needed to actually execute the
function’s code. This overhead occurs for small functions because
the execution time of a small function is less than the switching
time.
What we should take away is that the usage of the inline keyword
is only a request to the compiler, it is NOT a command. The
compiler might ignore it and at the same time, it might inline
functions as a form of optimization even if it was not requested.
It can bring some performance advantages in case the function
is small and often used, but at the same time, it can damage the
caching punctuality and bloat binary size.
References:

• CPlusPlus.com²⁴⁹
• Geeks For Geeks²⁵⁰
• Tutorials Point²⁵¹

Question 124: What do we catch?


Consider the following piece of code. What is going to be the output
if we uncomment the commented print statements in the two catch
blocks? And why so?

²⁴⁹http://www.cplusplus.com/articles/2LywvCM9/
²⁵⁰https://www.geeksforgeeks.org/inline-functions-cpp/
²⁵¹https://www.tutorialspoint.com/cplusplus/cpp_inline_functions.htm
Miscalleanous 236

1 #include <iostream>
2 #include <exception>
3
4 class SpecialException : public std::exception {
5 public:
6 virtual const char* what() const throw() {
7 return "SpecialException";
8 }
9 };
10
11 void a() {
12 try {
13 throw SpecialException();
14 } catch (std::exception e) {
15 // std::cout << "exception caught in a(): " << e.what\
16 () << '\n';
17 throw;
18 }
19 }
20
21 int main () {
22 try {
23 a();
24 } catch (SpecialException& e) {
25 // std::cout << "exception caught in main(): " << e.w\
26 hat() << '\n';
27 }
28 }

The output is apart from a compiler warning advising you not to


catch anything by value is:

1 exception caught in a(): std::exception\nexception caught\


2 in main(): SpecialException\n

Let’s have a look at the code once again. There is a new exception
Miscalleanous 237

type declared (1). In function a() we throw it (2) and then right
there we catch a quite generic std::exception by value (3). After
logging it, we rethrow the exception (4). In main(), we catch our
custom exception type by const reference (5):

1 #include <iostream>
2 #include <exception>
3
4 class SpecialException : public std::exception { // 1
5 public:
6 virtual const char* what() const throw() {
7 return "SpecialException";
8 }
9 };
10
11 void a() {
12 try {
13 throw SpecialException(); // 2
14 } catch (std::exception e) { // 3
15 std::cout << "exception caught in a(): " << e.what() \
16 << '\n';
17 throw; // 4
18 }
19 }
20
21 int main () {
22 try {
23 a();
24 } catch (SpecialException& e) { //5
25 std::cout << "exception caught in main(): " << e.what\
26 () << '\n';
27 }
28 }

When we caught and logged a standard exception by value


(3), we lost some of the information. Even though originally a
Miscalleanous 238

SpecialException was thrown (2), in order to squeeze it into an


std::exception variable, the compiler had to get rid of some parts
of that exception. In other words, it got sliced. Had we caught it by
reference, we would have kept its original type.
Yet, when you rethrow an exception simply by calling throw;, it
will rethrow the original exception which is SpecialException in
our case. If you catch an exception by value, the exception that was
the source of the copy will be rethrown. Hence any modification -
including the slicing - is lost.
So in the above case, the original exception is rethrown (4), not the
one we use within the catch block, but the one that left the try
block. We keep that narrower SpecialException.
A general rule of thumb is to always catch exceptions by reference
to avoid superfluous copies and to be able to make persistent
changes to the caught exception.

Question 125: What are the


differences between references and
pointers?
Both references and pointers can be used to pass even local vari-
ables from one function to another. If you use any of them, the
object referenced/pointed will not get copied and changes made to
their value/state in the called function will be visible in the caller
function as well.
Both pointers and references can also be used to gain performance
by saving the resources needed to copy big objects, e.g. when the
objects are passed into a function or when they are returned.
Despite these similarities, there are also some differences between
references and pointers.
Miscalleanous 239

1. Once a reference is created, it cannot be later changed to reference


another object; it cannot be reseated. On the other hand, this is often
done with pointers.
2. References cannot be NULL/nullptr. Pointers are often pointing
at the nullptr to indicate that they are not pointing to any valid
thing. Though, it’s worth noting that since C++17 there is also
std::optional in the standard library if we need to indicate that
an object is “not there”.
3. A reference must be initialized when declared. There is no such
restriction with pointers.
Due to the above limitations, references in C++ cannot be used for
implementing data structures like linked lists, trees, etc.
While the above restrictions prevail, at the same time references are
safer and easier to use:
1. Safer: Since references must be initialized, wild references like
wild pointers are unlikely to exist. It is still possible to have refer-
ences that don’t refer to a valid location, they are called dangling
references.
2. Easier to use: References don’t need the dereferencing operator
(*) to access the value. They can be used like normal variables. The
address of (&) operator is needed only at the time of declaration.
Also, members of an object reference can be accessed with dot
operator (.), unlike pointers where arrow operator (->) is needed
to access members.

Question 126: Which of the following


variable declarations compile and
what would be the value of a?
1. unsigned int a = 42;
Miscalleanous 240

2. unsigned int a{42};


3. unsigned int a = -42;
4. unsigned int a{-42};

1-2) That’s fairly simple. We declare an unsigned int in both cases


and we initialize them with a positive number. They both compile
and if you print the value of a, it’ll be 42 in both cases, there is
nothing to see here, move along.

3. This is getting more interesting. We initialize an unsigned


int with a negative number. But an unsigned integer is
supposed to represent a positive number. The signed -42
will be converted into an unsigned number. While the value
might depend on your platform, but in general the size of
an (unsigned) int is 32 bits, so the max value of an unsigned
number is 4294967295 (2**32-1, as we start from zero).

If you imagine a number line where there are no negative numbers,


left to zero will be the largest number so the value of -42 will
be the maximum value of an unsigned integer minus 41. Why
41 and not 42? Because -1 in fact equals the highest number that
we can represent. From there we have to step 41 to the left to
get to -42, which will be exactly std::numeric_limits<unsigned
int>::max()-41</unsigned>.

By the way, no matter what platform you use std::numeric_-


limits<unsigned int>::max()</unsigned> will give you the actual
highest value for an unsigned int and as you could guess you can
pass in other types as a template parameter.

4. Finally something that doesn’t compile. The uniformed or {}-


initialization introduced by C++11 doesn’t allow narrowing.
Depending on your compiler and its version, you either
get an error or a warning, but if you follow the industry
recommendations, you will treat warnings as errors, so it
shouldn’t matter.
Miscalleanous 241

Again, just to emphasize the fact. Initializing with = allows nar-


rowing conversions hence -42 became 4294967254, while if you {}-
initialize, your compiler will complain with either a warning or an
error.

Question 127: What will the line of


code below print out and why?
1 #include <iostream>
2
3 int main(int argc, char **argv) {
4 std::cout << 25u - 50;
5 return 0;
6 }

The answer is not -25. Rather, the - possibly surprising - answer is


4294967271, assuming 32 bit integers.
But why?
In C++, if the types of two operands differ from one another, then
the operand with the “lower” or “smaller” type will be promoted
to the type of the “higher” or “larger” type operand implicitly.
This promotion which is a special type of implicit conversions²⁵²,
uses the following type hierarchy (from highest type to lowest
type):

• long double
• double
• float
• unsigned long int
• long int
²⁵²https://en.cppreference.com/w/cpp/language/implicit_conversion
Miscalleanous 242

• unsigned int
• int

So when the two operands are, as in our example, 25u (unsigned


int) and 50 (int), the 50 is promoted to also being an unsigned int
(i.e., 50u).
The result of the operation will be of the type of the operands.
Therefore, the result of 25u - 50u will itself be an unsigned int
as well. So the result of -25 converts to 4294967271 when promoted
to being an unsigned int.
In fact, the result is the maximum value of an unsigned integer - (50
- 25 + 1). The plus one is there, because, between 1 and the maximum
value of an unsigned int, there is zero as well that we have to take
into account.

1 #include <iostream>
2 #include <numeric>
3
4 int main() {
5 auto a = std::numeric_limits<unsigned int>::max()+1 - 2\
6 5;
7 auto b = 25u - 50;
8 std::cout << std::boolalpha << (a == b) << '\n';
9 }

By the way, this is a nice instance of an integer underflow which is


considered a vulnerability²⁵³.
²⁵³https://cwe.mitre.org/data/definitions/191.html
Miscalleanous 243

Question 128: Explain the difference


between pre- and
post-increment/decrement
operators
C++ has a nice syntactical sugar to add or subtract 1 from a variable.
It’s so renowned that it appears even in the name of the language.
It’s the ++ (increment) and the -- (decrement) operators.
Both can be used as a prefix or a postfix operator.

1 int main() {
2 int a=5;
3 a++; // postfix increment; a is 6
4 ++a; // prefix increment; a is 7
5 a--; // postfix decrement; a is 6
6 --a; // prefix decrement; a is 5 again
7 }

So what’s the difference between the prefix and postfix operators?


The difference between the meaning of pre and post depends upon
how the expression is evaluated and the result is stored.
In the case of the pre-increment/decrement operator, the incremen-
t/decrement operation is carried out first, and then the result is
passed to an lvalue.
Whereas for post-increment/decrement operations, the lvalue is
evaluated first and then increment/decrement is performed.
To view this from another perspective, the prefix operator returns a
reference of the variable (T& T::operator++();), while the postfix
a copy (T T::operator++(int);).
This implies that while in the language name, there is a postfix
increment (C++), in our old raw for loops we got used to writing
Miscalleanous 244

i++ to increment the index, actually it’s better to use the prefix
operators by default unless we have a specific need for the postfix
and therefore a copy.
In most cases, you can only gain negligible performance with the
prefix operators, but in certain cases, when you have big objects
overloading these operators, you might gain a more significant
amount of time, especially when the returned value is not used.

Question 129: What are the final


values of a, b and c?
Consider the following small program. What are the final values of
a, b and c?

1 int main(){
2 int a, b, c;
3 a = 9;
4 c = a + 1 + 1 * 0;
5 b = c++;
6 return 0;
7 }

This short test covers both simple operator precedence and basic
knowledge about the increment operator that we discussed just
yesterday. The difficulty of this exercise comes from the line b =
c++;, you have to understand the difference between prefix and
postfix increments (++c vs c++).
Correct answer:
Miscalleanous 245

1 a = 9 // was not modified


2 b = 10
3 c = 11

In case you guessed that b is 11, let’s get back to the postfix
increment. What happens is that first, c’s value is copied into b
(both b and c is 10 at the moment) and just then c is incremented
and becomes 11. But that doesn’t affect b’s value. If instead, we had
the line b = ++c, both would become 11.
Another mistake could be that you guessed that c is either 0 or 1.
This will occur if there is an error in the evaluation of precedence.
Because the multiplication operator (*) has higher precedence than
addition (+), and the order of operation is otherwise left to right,
this equation translates to the following:

1 c = ((a + 1) + (1 * 0))
2 c = ((9 + 1) + 0)
3 c = 10 // correct (it will be incremented later)

If the correct order of precedence is not followed then one possibil-


ity is:

1 c = a + 1 + 1 * 0
2 c = 9 + 1 + 1 * 0
3 c = 10 + 1 * 0
4 c = 11 * 0
5 c = 0 // this is wrong

Question 130: Does this string


declaration compile?
Will the following code compile? If not, why not? If it does, why?
Miscalleanous 246

There is no trick, this is the whole application, foo is nowhere


declared as a global variable.

1 #include <iostream>
2
3 int main() {
4 std::string(foo);
5 }

Whether this code compiles or not depends on whether you treat


warnings as errors or not. Let’s assume you don’t handle warnings
as errors.
This code does exactly one thing, it creates an empty string and
assigns it to a variable called foo.
The following two lines mean the same:

1 std::string(foo);
2 std::string foo;

Why? Because according to the C++ standard whatever can be


interpreted as a declaration, it is interpreted as a declaration. It’s
also called the most vexing parse²⁵⁴.
Why does this matter? Who writes anything like that in real life?
We only have to complexify this example a little bit. Think about a
mutex.

²⁵⁴https://en.wikipedia.org/wiki/Most_vexing_parse
Miscalleanous 247

1 #include <mutex>
2
3 static std::mutex m_mutex;
4 static int shared_resource;
5
6 void increment_by_42() {
7 std::unique_lock<std::mutex>(m_mutex);
8 shared_resource += 42;
9 }

What is happening here?


At the beginning of this mail, you might have thought about that
okay, we create a temporary unique_lock, locking mutex m_mutex.
Well. No. I think you can tell on your own what’s happening there.
It creates a lock on the type of a mutex and called that lock m_mutex.
And nothing got locked.
But if you express your intentions by naming that lock, or if you
brace initialization it’ll work as expected.

1 #include <mutex>
2
3 static std::mutex m_mutex;
4 static int shared_resource;
5
6 void increment_by_42() {
7 std::unique_lock aLock(m_mutex); // this works fine
8 // std::unique_lock<std::mutex> {m}; // even this would\
9 work fine
10 shared_resource += 42;
11 }

By the way, using -Wshadow compiler option would have also caught
the problem by creating a warning. Treat all warnings as errors and
be safe!
Miscalleanous 248

Question 131: What are Default


Member Initializers in C++?
Default member initialization is available since C++ 11.
It lets you initialize class members where they are declared not in
the constructors.

1 class T {
2 public:
3 T()=default;
4 T(int iNum, std::string iText) : num(iNum), text(iText)\
5 {};
6
7 private:
8 int num{0}; // Default member initialization
9 std::string text{}; // Default member initialization
10 };

In the above example, the members num and text are initialized with
0 and with an empty string right where they are declared. As such,
we can keep the default constructor simpler.
It has at least two advantages.

• If you consistently follow this practice you will not have to


worry that you forgot to initialize something
• And you’ll not have to scroll anywhere else to find the default
value of the variable.

We can still override those values in any constructors. In case,


we initialize a member both in-place and in a constructor, the
constructor wins.
There is no performance overhead, the constructor will not reini-
tialize the member for a second time.
Miscalleanous 249

You might ask if it means that the members will first be assigned
to their default value and then reassigned with the values from the
constructor. The compiler is smart enough to know which value
to use and it avoids the extra assignments. The C++ Core guide-
lines²⁵⁵ also encourages us to use default member initialization for
initialization data members instead of the default constructor.

Question 132: What is the most


vexing parse?
The most vexing parse is a specific form of syntactic ambiguity
resolution in the C++ programming language. The term was used
by Scott Meyers in Effective STL²⁵⁶. It is formally defined in section
8.2 of the C++ language standard.
It means that whatever that can be interpreted as a function
declaration, will be interpreted as a function declaration.
Take the following example:

1 std::string foo();

Probably this is the simplest form of the most vexing parse. The
unsuspecting reader might think that we just declared a string
called foo and called its default constructor, so initialized it as an
empty string.
Then, for example, when we try to call empty() on it, and we have
the following error message (with gcc):

²⁵⁵https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c45-
dont-define-a-default-constructor-that-only-initializes-data-members-use-in-class-member-
initializers-instead
²⁵⁶https://www.sandordargo.com/blog/2020/08/26/effective-stl
Miscalleanous 250

1 main.cpp:18:5: error: request for member 'empty' in 'foo'\


2 , which is of non-class type 'std::string()' {aka 'std::_\
3 _cxx11::basic_string<char>()'

What happened is that the above line of code was interpreted as a


function declaration. We just declared a function called foo, taking
no parameters and returning a string. Whereas we only wanted to
call the default constructor.
This can give a kind of headache to debug even if you know about
the most vexing parse. Mostly because you see the compiler error
on a different line, not when you declare your <del>varibale</del>
function, but when you try to use it.
This can be fixed very easily. You don’t need to use parentheses at
all to declare a variable calling its default constructor. But since
C++11, if you want you can also use the {}-initialization. Both
examples are going to work just fine:

1 std::string foo;
2 std::string bar{};

Now let’s have a look at a bit more interesting example:

1 #include <iostream>
2 #include <string>
3
4 struct MyInt {
5 int m_i;
6 };
7
8 class Doubler {
9 public:
10 Doubler(MyInt i) : my_int(i) {}
11 int doubleIt() {
12 return my_int.m_i*2;
Miscalleanous 251

13 }
14
15 private:
16 MyInt my_int;
17 };
18
19 int main() {
20 int i=5;
21 Doubler d(MyInt(i));
22
23 std::cout << d.doubleIt() << std::endl;
24 }

There are 3 ways to fix it:

• Declare the MyInt object outside of the call, on the previous


line, but then it won’t be a temporary anymore.
• Replace any or both of the parentheses with the brace initial-
ization. Both Doubler d{MyInt(i)}; or Doubler d(MyInt{i})
would work, just like Doubler d{MyInt{i}}. And this third
one is inconsistent at least in how we call the constructors.
The potential downside is that this only works since C++11.
• If you are using an older version of C++ than C++11, you can
add an extra pair of parentheses around the argument that is
meant to be sent to the constructor: Doubler d((MyInt(i))).
This also makes it impossible to parse it as a declaration.

Question 133: Does this code


compile? If yes, what does it do? If
not, why not?
Does this code compile? If yes, what does it do? If not, why not?
Miscalleanous 252

1 class MyObject {
2 public:
3 void doSomething() {}
4 private:
5 // ...
6 };
7
8 int main() {
9 MyObject o();
10 o.doSomething();
11 }

The code doesn’t compile and the error message is this:

1 main.cpp: In function 'int main()':


2 main.cpp:11:4: error: request for member 'doSomething' in\
3 'o', which is of non-class type 'MyObject()'
4 11 | o.doSomething();
5 | ^~~~~~~~~~~

So o in of non-class type MyObject(). In other words, o is a function


taking no parameters and returning a MyObject.
This is the infamous most-vexing parse that we discussed a month
ago. It is a specific form of syntactic ambiguity resolution in the
C++ programming language. The term was used by Scott Meyers
in Effective STL²⁵⁷. It is formally defined in section 8.2 of the C++
language standard. It means that whatever that can be interpreted
as a function declaration, will be interpreted as a function declara-
tion.
Hence the line MyObject o(); is interpreted not as a variable
declaration, but a function declaration.
What would happen if we called o?
²⁵⁷https://www.sandordargo.com/blog/2020/08/26/effective-stl
Miscalleanous 253

1 class MyObject{
2 public:
3 void doSomething() {}
4 private:
5 // ...
6 };
7
8 int main() {
9 MyObject o();
10 o();
11 }
12 /*
13 main.cpp:(.text.startup+0x5): undefined reference to `o()'
14 collect2: error: ld returned 1 exit status
15 */

Before we saw an error at compilation time, now we see it at linking


time. As said, we declared a function, but it is nowhere defined,
hence the undefined reference linking error.
The simplest way to fix the error is to simply omit the braces:
MyObject o;. Since C++11 we can also use aggregate or‘. The
potential downside is that it only works since C++11, but that’s
less and less an error.

Question 134: What is std::string_view


and why should we use it?
std::string_view has been introduced by C++17 and a typical
implementation needs two information. The pointer to a character
sequence and its length. The character sequence can be both a
C++ or a C-string. After all, std::string_view is a non-owning
reference to a string.
Miscalleanous 254

This type is needed because it’s quite cheap to copy it as it only


needs the above-mentioned copy and its length. We can effectively
use it for read operations, and besides it received the remove_pre-
fix²⁵⁸ and remove_suffix²⁵⁹ modifiers.
As you might have guessed, the reason why string_view has
these two modifiers, because it’s easy to implement them without
modifying the underlying character sequence. Either the start of
the character sequence or its length should be modified.\n \nIf your
function doesn’t need to take ownership of the string argument
and you only need read operations (plus the above mentioned two
modifiers) use a string_view instead. In fact, string_view should
replace all const std::string& parameters.
There is one drawback though. As under the hood you can either
have a std::string or a std::string_view, you lose the implicit
null termination. If you need that, you have to stick with std::string
(const&)
The above-mentioned fact that it’s quite cheap to copy, and that it
has one less level of pointer indirection than a std::string& can
bring a significant performance gain. If you’re interested in the
details, please read this article²⁶⁰.

Question 135: How to check if a


string starts or ends with a certain
substring?
Unlike Python or other languages, C++’s string type didn’t have
starts_with/ends_with functions that could easily tell if a string
starts with a certain prefix or ends with a given suffix.
²⁵⁸https://en.cppreference.com/w/cpp/string/basic_string_view/remove_prefix
²⁵⁹https://en.cppreference.com/w/cpp/string/basic_string_view/remove_suffix
²⁶⁰https://www.modernescpp.com/index.php/c-17-avoid-copying-with-std-string-view
Miscalleanous 255

This has changed with C++20, where starts_with²⁶¹ and ends_-


with²⁶² were added both to std::sting and std::string_view and
performing such checks became extremely simple and readable!

1 #include <iostream>
2 #include <string>
3
4 int main() {
5 std::string s{"what a string"};
6 std::cout << std::boolalpha;
7 std::cout << s.starts_with("what") << '\n';
8 std::cout << s.ends_with("not") << '\n';
9 }
10 /*
11 true
12 false
13 */

Before C++20 it was still easy to perform similar tests, though a bit
less readable and more verbose.
In order to check if a string starts with a prefix, one could use
std::string::find and check if the returned position is one of the
first characters, 0.
To verify the suffix is a bit more complex and less readable. We can
use std::string::compare. After making sure that the string to be
checked is at least as long as the suffix we want to verify, we have
to pass the starting position of the potential suffix, its length and
the suffix itself.
Wrapping it to a function it looks like this:

²⁶¹https://en.cppreference.com/w/cpp/string/basic_string/starts_with
²⁶²https://en.cppreference.com/w/cpp/string/basic_string_view/ends_with
Miscalleanous 256

1 #include <iostream>
2 #include <string>
3
4 bool ends_with(const std::string& str, const std::string&\
5 suffix) {
6 if (str.size() >= suffix.size()) {
7 return str.compare(str.size() - suffix.size(), suffix\
8 .size(), suffix) == 0;
9 }
10 return false;
11 }
12
13 int main() {
14 std::string s{"what a string"};
15 std::cout << std::boolalpha;
16 std::cout << (s.find("what") == 0) << '\n';
17 std::cout << (s.find("a") == 0) << '\n';
18 std::cout << ends_with(s, "string") << '\n';
19 std::cout << ends_with(s, "what") << '\n';
20 }

If you have access to boost, you might use boost::algorithm::starts_-


with/boost::algorithm::ends_with:

1 #include <iostream>
2 #include <string>
3 #include <boost/algorithm/string/predicate.hpp>
4
5 int main() {
6 std::string s{"what a string"};
7 std::cout << std::boolalpha;
8 std::cout << boost::algorithm::starts_with(s, "what") <\
9 < '\n';
10 std::cout << boost::algorithm::starts_with(s, "a") << '\
11 \n';
Miscalleanous 257

12 std::cout << boost::algorithm::ends_with(s, "string") <\


13 < '\n';
14 std::cout << boost::algorithm::ends_with(s, "what") << \
15 '\n';
16 }

References:

• Boost Header²⁶³
• C++ Reference: compare²⁶⁴
• C++ Reference: starts_with²⁶⁵
• C++ Reference: ends_with²⁶⁶
• Find out if string ends with another string in C++ - Stackover-
flow²⁶⁷

Question 136: What is RVO?


RVO stands for return value optimization.
It is a compiler optimization that involves eliminating the \ntem-
porary object created to hold a function’s return value. RVO is
particularly notable for being allowed to \nchange the observable
behaviour of the resulting program by the C++ standard.
In general, the C++ standard allows a compiler to perform any
optimization, provided the resulting executable exhibits the same
observable \nbehaviour as if (i.e. pretending) all the requirements
of the standard have been fulfilled. This is commonly referred \nto
as the “as-if rule”. The term return value optimization refers to a
²⁶³https://www.boost.org/doc/libs/1_72_0/doc/html/string_algo/reference.html#header.
boost.algorithm.string.predicate_hpp
²⁶⁴https://en.cppreference.com/w/cpp/string/basic_string/compare
²⁶⁵https://en.cppreference.com/w/cpp/string/basic_string/starts_with
²⁶⁶https://en.cppreference.com/w/cpp/string/basic_string_view/ends_with
²⁶⁷https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-
string-in-c
Miscalleanous 258

special clause in the C++ standard \nthat goes even further than the
“as-if” rule: an implementation may omit a copy operation resulting
from a \nreturn statement, even if the copy constructor has side
effects.

1 #include <iostream>
2
3 struct C {
4 C() = default;
5 C(const C&) {
6 std::cout << "A copy was made." << std::endl;
7 }
8 };
9
10 C f() {
11 return C();
12 }
13
14 int main() {
15 std::cout << "Hello World!" << std::endl;
16 C obj = f();
17 }

When the compiler sees a variable in the calling function (that will
be constructed from the return value), and a variable in the called
function (that will be returned),\nit realizes it doesn’t need both
variables. Under the covers, the compiler passes the address of the
calling \nfunction’s variable to the called function.
To quote the C++98 standard, “Whenever a temporary class \nob-
ject is copied using a copy constructor … an implementation is
permitted to treat the original and the copy as two \ndifferent ways
of referring to the same object and not perform a copy at all, even
if the class copy constructor or \ndestructor have side effects. For a
function with a class return type, if the expression in the return
statement is \nthe name of a local object … an implementation
Miscalleanous 259

is permitted to omit creating the temporary object to hold the


\nfunction return value…” (Section 12.8 [class.copy], paragraph 15
of the C++98 standard. The C++11 standard has \nsimilar language
in section 12.8, paragraph 31, but it’s more complicated.)
References:
- Abseil.io²⁶⁸
- Wikipedia: copy elision²⁶⁹

Question 137: How can we ensure


the compiler performs RVO?
Let’s remind us from yesterday about what RVO is!
It is an optimization done by the compiler that involves eliminating
the temporary object created to hold a function’s return value. RVO
is particularly notable for being allowed to change the observable
behaviour of the resulting program by the C++ standard.
In practice, it means that when RVO is applied, fewer copies of an
object will be made, the copy constructor will be invoked fewer
times. The gain can be considerable for some big objects.
In order to have RVO applied the returned object has to be con-
structed on a return statement. A function can have multiple exits,
it’s not a problem as long as the construction happens on the return
statement.

²⁶⁸https://abseil.io/tips/11
²⁶⁹https://en.wikipedia.org/wiki/Copy_elision#Return_value_optimization
Miscalleanous 260

1 SomeBigObject f() {
2 if (...) {
3 return SomeBigObject{...};
4 } else {
5 return SomeBigObject{...};
6 }
7 }

What is NRVO and when can it happen?


N in NRVO stands from Named, so we speak about Named Return
Value Optimization. Copies of temporary objects can be spared
even if the returned object has a name and is therefore not con-
structed on the return statement. The requirement for an NRVO is
to have one single object to return even if there are multiple exit
points.

1 SomeBigObject f() {
2 SomeBigObject result{...};
3 if (...) {
4 return result;
5 }
6 //...
7 return result;
8 }

So NRVO won’t happen here:


Miscalleanous 261

1 SomeBigObject f(...) {
2 SomeBigObject object1{...}
3 SomeBigObject object2{...};
4 if (...) {
5 return object1;
6 } else {
7 return object2;
8 }
9 }

References:

• Abseil.io²⁷⁰
• Fluent C++²⁷¹

Question 138: What are the primary


and mixed value categories in C++?
A C++ expression - such as an operator with its operands, a literal,
a variable name, etc. - is always characterized by two independent
properties: a type and a value category. Each expression belongs to
one of the three primary value categories:

• lvalue,
• prvalue,
• xvalue.

lvalue
An lvalue is an expression whose evaluation determines the identity
of an object, bit-field, or function whose resources cannot be reused.
²⁷⁰https://abseil.io/tips/11
²⁷¹https://www.fluentcpp.com/2016/11/28/return-value-optimizations/
Miscalleanous 262

lvalues could only appear on the left-hand side of an assignment


operator.
The address of an lvalue may be taken by the built-in address-of
operator. A modifiable lvalue may be used as the left-hand operand
of the built-in assignment and compound assignment operators. An
lvalue may be used to initialize an lvalue reference; this associates
a new name with the object identified by the expression.
Some examples to give you the idea:

• the name of a variable, a function, member. Even if the vari-


able’s type is an rvalue reference, the expression consisting of
its name is an lvalue expression;
• a function call or an overloaded operator expression, whose
return type is lvalue reference
• all built-in assignment and compound assignment expres-
sions (a = b;, a*=b;, etc)
• cast expressions to lvalue reference type
• …

prvalue

A prvalue (“pure” rvalue) is an expression whose evaluation either

• computes the value of the operand of an operator or is a void


expression (such prvalue has no result object), or
• initializes an object or a bit-field (such prvalue is said to have
a result object). With the exception of decltype, all class and
array prvalues have a result object even if it is discarded. The
result object may be a variable, an object created by new-
expression, a temporary created by temporary materializa-
tion, or a member thereof;
Miscalleanous 263

A prvalue cannot be polymorphic: the dynamic type of the object


it denotes is always the type of the expression. A non-class non-
array prvalue cannot be cv-qualified. A prvalue cannot have an
incomplete type. A prvalue cannot have abstract class type or an
array thereof.
Some examples:

• literals, except for string literals that are lvalues


• a function call or an overloaded operator expression, whose
return type is non-reference
• all built-in arithmetic and logical expressions
• lambda expressions
• …

xvalue

An “eXpiring” value is an expression whose evaluation determines


the identity of an object, bit-field, or function whose resources can
be reused. (Unlike lvalues whose resources cannot be reused.)
Some examples:

• a function call or an overloaded operator expression, whose


return type is an rvalue reference to an object, such as
std::move(x);
• a cast expression to rvalue reference to object type, such as
static_cast<char&&>(x)
• …

Reference:

• C++ Reference: value categories²⁷²


²⁷²https://en.cppreference.com/w/cpp/language/value_category
Miscalleanous 264

Question 139: Can you safely


compare signed and unsigned
integers?
No, you cannot. You should avoid comparing signed and unsigned
integers in order to avoid bad results.

1 int x = -3;
2 unsigned int y = 7;
3
4 // unsigned result, possibly 4294967286
5 std::cout << x - y << '\n';
6
7 // unsigned result: 4
8 std::cout << x + y << '\n';
9
10 // unsigned result, possibly 4294967275
11 std::cout << x * y << '\n';
12 std::cout << std::boolalpha;
13 std::cout << "-3 < 7: " << (x < y) << '\n'; // false
14 std::cout << "-3 <= 7: " << (x <= y) << '\n'; // false
15 std::cout << "-3 > 7: " << (x > y) << '\n'; // true
16 std::cout << "-3 => 7: " << (x >= y) << '\n'; //true

The reason behind is that x which is an int is cast into an unsigned


int Due to the conversion, x becomes - depending on your platform
- 4294967293 (2^32 -3).
You should avoid comparing signed with unsigned integers unless
you use C++20 and you have access to std::cmp_equal and to its
friends. For more details, check out the references.
References:
Miscalleanous 265

• C++ Core Guidelines: ES.100: Don’t mix signed and unsigned


arithmetic²⁷³
• Modernes C++: Safe Comparisons of Integrals with C++20²⁷⁴

Question 140: What is the return


value of main and what are the
available signatures?
Though in some cases you might see void, it is not correct. The
return type of the main function of a C++ program is int.
If the program finishes successfully main returns 0, otherwise a non-
zero number. Based on this return type the OS knows if the program
succeeded or not. Though there is no standard on what different
integer values would mean.
Even though the return type of main is int, in this case, a return
can be omitted and it will be automatically treated as 0, so it will
be considered a successful return.
Main has two valid signatures:

• int main() where there is no arguments passed in


• int main(int argc, char **argv) or equivalent such as int
main(int argc, char *argv[])

In this latter case, argc represents the number of command-line


arguments passed into C++ and in argv you can find the program
name and the arguments. When argc is bigger than 0, argv[0] will
be the program name and the rest are the arguments.
²⁷³https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es100-dont-mix-
signed-and-unsigned-arithmetic
²⁷⁴https://www.modernescpp.com/index.php/safe-comparisons-of-integrals-with-c-20
Miscalleanous 266

As an example, if you execute: ./myProg foo bar 'b a z', argc


will be 3 and argv will be ["myProg", "foo", "bar", "b a z"].
References:

• Core Guidelines: F46²⁷⁵


• Stackoverflow: What should main() return in C and C++?²⁷⁶
• CPlusPlus.com²⁷⁷

Question 141: Should you prefer


default arguments or overloading?
In most cases, default arguments should be preferred. There is
no technical or performance reason behind it, it’s more practical.
While it’s possible that one overload simply calls the other with the
“default” as in the following example, but there is no guarantee that
this will be respected and we won’t end up with duplicated code, or
worse with diverging behaviours when the intention was to have
the same behaviour.
“default”: values with overloading

1 int foo(int a, int b) {


2 //...
3 }
4
5 int foo(int a) {
6 return foo(a, 0); // 0 acts as default value for b
7 }

A real default argument:


²⁷⁵https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f46-int-is-the-return-
type-for-main
²⁷⁶https://stackoverflow.com/questions/204476/what-should-main-return-in-c-and-c
²⁷⁷http://www.cplusplus.com/articles/DEN36Up4/
Miscalleanous 267

1 int foo(int a, int b=0) {


2 // ...
3 }

Though if you are a library maintainer, you might have to take


binary compatibility into consideration. Adding a new parameter
to a function, even if it has a default argument, breaks binary
compatibility. On the other hand, adding a new overload does not
break binary compatibility.
Therefore if you are a library maintainer and binary compatibility
is something you care about, prefer the overloads and take a note
to merge them once you plan to release a new major version.
References:

• Core Guidelines: F51²⁷⁸


• KDE Community Wiki: Policies/Binary Compatibility Issues
With C++²⁷⁹

Question 142: How many variables


should you declare on a line?
One and only one. In most cases. Syntactically it’s fine to declare
just as many as you want, but declaring only one will increase
readability and avoid mistakes.
Consider such a line:

1 char *p, c, a[7], *pp[7], **aa[10];

It’s pretty difficult to know what goes on and easy to make mistakes.
When you start adding initializations, it becomes more of a mess.
²⁷⁸https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f51-where-there-is-a-
choice-prefer-default-arguments-over-overloading
²⁷⁹https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B
Miscalleanous 268

1 int a, b = 3;

How is a initialized?
It’s not initialized, but inexperienced colleagues might think, that
it should be 3.
If you initialize each variable on its own line, you won’t have
such a misunderstanding, plus it’s much easier to add meaningful
comments to the code if you want to explain the intention of the
variable.
When I see multiple declarations on the same line, most often
initialization comes line by line, variable by variable a few lines
below, which is a complete waste of assignment.
Declare and if possible initialize one variable per line to spare some
assignments and to boost readability.
Reference:

• Core Guidelines: ES10²⁸⁰

Question 143: Should you prefer a


switch statement or chained if
statements?
Prefer the switch statement. There are multiple reasons.
A switch statement is more readable, even if you take into account
all the necessary break statements.
Besides, usually, a switch statement can be better optimized.
²⁸⁰https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es10-declare-one-
name-only-per-declaration
Miscalleanous 269

But here comes the most important reason. It might be that you
switch over an int, but most probably it can be turned into an enum.
If you do so, and you avoid having a default case, the compiler will
emit a warning in case you don’t cover all the different cases in the
enum.

This is also a good reason not to have a default case, just to get you
covered. Imagine that one day, you add a new case to the enum and
you forget to update all the switch statements in your codebase. If
you don’t use defaults and you treat warnings as errors, your code
simply won’t compile.

1 enum class Color { Red, Green, Blue, Yellow };


2
3 // ...
4 Color c = getColor();
5 switch (c) {
6 case Color::Red: break;
7 case Color::Green: break;
8 };
9 /*
10 main.cpp:9:12: warning: enumeration value 'Blue' not hand\
11 led in switch [-Wswitch]
12 main.cpp:9:12: warning: enumeration value 'Yellow' not ha\
13 ndled in switch [-Wswitch]
14 */

References:

• Core Guidelines: ES70²⁸¹


• Correct by Construction by Matt Godbolt²⁸²
²⁸¹https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es70-prefer-a-switch-
statement-to-an-if-statement-when-there-is-a-choice
²⁸²https://www.youtube.com/watch?v=nLSm3Haxz0I
Miscalleanous 270

Question 144: What are include


guards?
Include or header guards are there to prevent that the same header
file is included multiple times. If a header is included in multiple
files, then the same entities would be defined multiple times which
is a clear violation of the one definition rule²⁸³. As such, the code
wouldn’t compile.
In order to understand this better, we have to remind ourselves that
before the compilation the preprocessor replaces all the #include
statements with the textual copy of the included file.
Hence, we must use include guards in all our header files:

1 #ifndef SOME_UNIQUE_NAME_HERE
2 #define SOME_UNIQUE_NAME_HERE
3
4 // your declarations (and certain types of definitions) h\
5 ere
6
7 #endif

At the first inclusion as SOME_UNIQUE_NAME_HERE is not defined yet,


it will be defined and the content of the header file will be copied.
At the second inclusion, #ifndef SOME_UNIQUE_NAME_HERE will be
evaluated to false and such the preprocessor jumps right after
#endif, so at the end of the file.

With most modern compilers you can simply start your header file
with #pragma once and avoid the above syntax to get the same
results.
References:
²⁸³https://en.wikipedia.org/wiki/One_Definition_Rule
Miscalleanous 271

• Core Guidelines: SF8²⁸⁴


• LearnCpp: Header guards²⁸⁵
• Wikipedia: include guard²⁸⁶
• One Definition Rule²⁸⁷

Question 145: Should you use angle


brackets(<filename>) or double
quotes(“filename”) to include?
As almost always in life, the answer is it depends!
For files that are in the same project, files that exist at the relative
path to the including file, one should use the double-quotes.
For files from the standard library, or in fact, from any library that
you depend on, the angle brackets form should be preferred.

1 // foo.cpp:
2 // From the standard library, requires the <> form
3 #include <string>
4
5 // A file that is not locally relative,
6 // included from another library; use the <> form
7 #include <some_library/common.h>
8
9 // A file locally relative to foo.cpp
10 // in the same project, use the "" form
11 #include "foo.h"
12
13
²⁸⁴https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#sf8-use-include-
guards-for-all-h-files
²⁸⁵https://www.learncpp.com/cpp-tutorial/header-guards/
²⁸⁶https://en.wikipedia.org/wiki/Include_guard
²⁸⁷https://en.wikipedia.org/wiki/One_Definition_Rule
Miscalleanous 272

14 // A file locally relative to foo.cpp


15 // in the same project, use the "" form
16 #include "foo_utils/utils.h"

If you use double quotes, it will first look up the file in the local
relative path first and then if it failed to file a match, it will look for
it anywhere else it’s possible.
This also means that if you use double quotes to include a file from
another library and a file with the same name is created in your
local project at the same relative path, you will have a bad surprise
while compiling.
These are the commonly followed best practices, for exact informa-
tion, it’s worth checking your compilers implementation.
References:

• Core Guidelines: SF8²⁸⁸


• Stackoverflow: What is the difference between #include <file-
name>and #include “filename”?</filename>²⁸⁹

Question 146: How many return


statements should you have in a
function?
While some would insist that you should only have one return
statement for better understandability, it’s counter-productive.
It’s true that with multiple return statements you’ll have multiple
exit points that you have to consider when you try to understand
²⁸⁸https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#sf8-use-include-
guards-for-all-h-files
²⁸⁹https://stackoverflow.com/questions/21593/what-is-the-difference-between-include-
filename-and-include-filename
Miscalleanous 273

what a function does. But this is only a problem if your function


is too big. If you write short functions, let’s say what fits a screen
(use a nice, big font size) then this is not a problem.
Besides, with multiple return statements, it’s easy to create some
guards on top of the function:

1 void foo(MyClass* iMyClass) {


2 if (iMyClass == nullptr) {
3 return;
4 }
5 // do stuff
6 }

With multiple return statements, you can avoid unnecessarily


convoluted code and the introduction of extra state variables.
References:

• Core Guidelines: NR2²⁹⁰

²⁹⁰https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#nr2-dont-insist-to-
have-only-a-single-return-statement-in-a-function

You might also like