CPP Interview
CPP Interview
Sandor Dargo
This book is for sale at http://leanpub.com/cppinterview
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Lambda functions . . . . . . . . . . . . . . . . . . . . . . . . 65
CONTENTS
C++20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Question 64: What are concepts in C++? . . . . . . . . . 132
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
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
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.
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
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:
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
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:
References:
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:
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:
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;
References:
• C++ Reference¹¹
• Effective Modern C++ by Scott Meyers¹²
• Simplify C++¹³
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:
¹⁴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 class A {
2 static MyType s_var;
3 };
4
5 MyType A::s_var = value;
a const enum, the static member can be initialized inside the class
definition:
1 class A {
2 static int s_var{42};
3 };
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 };
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
References:
• C++ FAQ²⁰
• ModernesC++²¹
²⁰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 }
1 std::string theLogger() {
2 static std::string aLogger = "aNiceLogger";
3 return aLogger;
4 }
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.
1 class MyClass {
2 public:
3 void doSomething() const;
4 void doSomething();
5 };
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 };
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 */
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.
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 };
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 };
1 class Base {
2 void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // Error: Base::foo is not virtual
7 };
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 };
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 };
1 SUVFactoryLine sf;
2 Car* car = sf.produce();
3 SUV* suv = dynamic_cast<SUV*>(car);
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 };
1 SUVFactoryLine sf;
2 SUV* car = sf.produce();
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 }
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?
1 TeachingAssistant aTeachingAssistant;
2 Person& student = static_cast<Student&>(aTeachingAssistan\
3 t);
4 Person& worker = static_cast<Worker&>(aTeachingAssistant);
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 {};
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
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();
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
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::
⁴¹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
References::
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
References::
⁴⁴https://isocpp.org/wiki/faq/private-inheritance
Polymorphism, inheritance and virtual functions 49
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
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
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
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
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⁵⁷
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 }
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⁵⁹
• copy constructor
• copy assignment operator
• move constructor
• move assignment operator
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 */
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 */
⁶³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⁶⁴.
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 // 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 }
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 int num;
2 auto l = [num](){}
⁶⁸parameters
Lambda functions 69
1 int num=42;
2 auto l = [&num](){};
1 auto l = [this](){};
1 auto l = [num=5](){};
1 int num=42;
2 auto l = [&num2=num](){};
1 auto l = [*this](){};
References:
⁶⁹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.
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:
⁷³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 */
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 }
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
1 class SgWithMove {
2 // ...
3 };
4
5 SgWithMove foo() {
6 // ...
7 }
8
9 int main() {
10 SgWithMove o;
11 o = foo();
12 }
1 class SgWithMove {
2 // ...
3 };
4
5 const SgWithMove bar() {
6 // ...
7 }
8
9 int main() {
10 SgWithMove o;
11 o = bar();
12 }
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?
1 void f() {
2 MyObject o;
3 const auto& aRef = o.getSomethingConstRef();
4 aRef.doSomething();
5 }
⁸¹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 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 };
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:
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
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:
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 }
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 */
Reference:
⁹¹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.
1 std::string text{"abcefg"};
2
3 Point a{5,4,3}; //Point is class taking 3 integers as par\
4 ameters
⁹²https://en.cppreference.com/w/cpp/language/aggregate_initialization
Some best practices in modern C++ 92
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};
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 }
Reference:
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 }
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 };
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 };
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};
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 }
• C++ Reference⁹⁷
• Effective Modern C++ by Scott Meyers⁹⁸
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;
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
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:
⁹⁹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
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.
References:
• C++ Reference¹⁰³
• Effective Modern C++ by Scott Meyers¹⁰⁴
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 };
• 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
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 */
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 */
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.
On the other hand, resources that are not acquired before using
them, are not part of RAII, such as
- 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 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();
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:
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);
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
• C++ Reference¹¹⁶
• Effective Modern C++ by Scott Meyers¹¹⁷
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);
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:
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 */
• 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 }
1 T* fun() {
2 if (!someCondition()) {
3 return nullptr;
4 }
5 return getT();
6 }
¹²³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.
References:
References:
1 template<typename T>
2 void f(T&& param);
3
4 // universal reference
5 auto&& v2 = v; // universal reference
¹²⁶https://en.cppreference.com/w/cpp/utility/forward
¹²⁷https://amzn.to/38gK5bd
References, universal references, a bit of a mixture 127
• ISOCpp.org¹²⁸
• Effective Modern C++ by Scott Meyers: Item 24¹²⁹
• 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
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
¹³⁵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.
1 template<typename T>
2 concept integral = std::is_integral<T>::value;
References:
Reference:
• C++ Reference¹⁴³
¹⁴³https://en.cppreference.com/w/cpp/language/attributes
C++20 136
• ModernesC++¹⁴⁵
• CppReference¹⁴⁶
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
• C++ Reference¹⁵³
• Fluent C++¹⁵⁴
• Modernes C++¹⁵⁵
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:
• C++ Reference¹⁵⁶
• Fluent C++¹⁵⁷
• Modernes C++¹⁵⁸
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:
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++¹⁶²
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:
• 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
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 };
• C++ Reference¹⁶⁷
• Effective Modern C++ by Scott Meyers¹⁶⁸
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¹⁷⁰
¹⁷¹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.
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 };
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 };
1 SUVFactoryLine sf;
2 Car* car = sf.produce();
3 SUV* suv = dynamic_cast<SUV*>(car);
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 };
1 SUVFactoryLine sf;
2 SUV* car = sf.produce();
¹⁷⁸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 };
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 };
¹⁷⁹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 };
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 };
1 class Base {
2 void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // Error: Base::foo is not virtual
7 };
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 };
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 }
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
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 */
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.
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 }
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 }
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;
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?
1 TeachingAssistant ta;
2 Person& student = static_cast(ta);
3 Person& worker = static_cast(ta);
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 {};
reference here¹⁸¹.
¹⁸¹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.
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
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¹⁸⁵
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};
• 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
1 //...
2 if (buy) {
3 // do buy
4 // ...
5 } else {
6 // do sell
7 // ...
8 }
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:
• 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
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 }
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
References:
²⁰⁰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 };
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 };
1 int main() {
2 StillAbstractClass* s = new Leaf();
3 }
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.
• 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
1 class Base {
2 void foo() {
3 X& underlying = static_cast<X&>(*this);
4 // now you can access X's public interface
5 }
6 };
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²⁰⁶
²⁰⁷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.
• ill-formed
• ill-formed, no diagnostic required
• implementation-defined behaviour
• unspecified behaviour
• undefined behaviour
References:
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.
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 }
• Wikipedia²¹⁵
• GeeksForGeeks²¹⁶
• Undefined behaviour in the STL - Sandor Dargo (C++ on Sea
2020)²¹⁷
References:
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:
²²⁴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 #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:
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
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?
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
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.
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
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 }
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.
References:
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
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:
• 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
• 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.
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 }
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
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 */
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 };
1 int main() {
2 Derived *d = new Derived;
3 Base *b = d;
4 b->fun();
5 d->fun();
6 }
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.
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.
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 }
1 Derived::fun 42
2 Derived::fun 13
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:
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 };
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
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
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²⁵¹
²⁴⁹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 }
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 }
• long double
• double
• float
• unsigned long int
• long int
²⁵²https://en.cppreference.com/w/cpp/language/implicit_conversion
Miscalleanous 242
• unsigned int
• int
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 }
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 }
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.
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
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)
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
1 #include <iostream>
2
3 int main() {
4 std::string(foo);
5 }
1 std::string(foo);
2 std::string foo;
²⁵⁴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 }
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
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.
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.
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 std::string foo;
2 std::string bar{};
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 }
1 class MyObject {
2 public:
3 void doSomething() {}
4 private:
5 // ...
6 };
7
8 int main() {
9 MyObject o();
10 o.doSomething();
11 }
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 */
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 }
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
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²⁶⁷
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
²⁶⁸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 }
1 SomeBigObject f() {
2 SomeBigObject result{...};
3 if (...) {
4 return result;
5 }
6 //...
7 return result;
8 }
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++²⁷¹
• 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
prvalue
xvalue
Reference:
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
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:
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.
References:
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
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
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
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:
²⁹⁰https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#nr2-dont-insist-to-
have-only-a-single-return-statement-in-a-function