Daily C++ Interview. Prepare Yourself For Your Next Interview One Question A Day
Daily C++ Interview. Prepare Yourself For Your Next Interview One Question A Day
Sandor Dargo
* * * * *
This is a Leanpub book. Leanpub empowers authors and publishers with the
Lean Publishing process. Lean Publishing is the act of publishing an in-
progress ebook using lightweight tools and many iterations to get reader
feedback, pivot until you have the right book and build traction once you do.
* * * * *
I recommend you take a question every day, try to answer it on your own and
then read the answer. The depth of the answers is limited due to space and
time reasons, but for almost every question you get a list of references, so
you’re more than welcome to continue the quest.
Before starting to take the questions, let’s discuss the different kinds of
interviews and see how Daily C++ Interview will help you nail them.
The different typical interview
processes
In order to better understand where Daily C++ Interview can help you
prepare for your next job interview, let’s differentiate between two typical
interview processes.
This first round might have been preceded by a 0th round with some
takeaway exercise. The idea behind is that if you cannot prove a certain level
of expertise, they don’t want to waste their time on the applicant. Sometimes
this exercise is way too long and many of us would outright reject such
homework. If it’s more reasonable, you can complete it in an hour or two.
After the screening, there is a technical round that is usually quite broad and
there is not a lot of time to go deep on the different topics. It’s almost sure
that you’ll get an easy or medium-level Leetcode-style coding exercise. For
those, you must be able to reason about algorithmic complexities and it’s also
very useful if you are more than familiar with the standard library of your
language. Apart from a coding exercise, expect more general questions about
your chosen language.
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.
Given the several rounds, scheduling conflicts and sometimes flying in for
the last round, such a process can go quite long, it can easily take at least a
month if not two.
As you can see if you use braced initializers, auto is forced into creating 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.
References:
In such situations, it’s better either not to use auto at all, or use the idiom that
Meyers calls the the explicitly typed initializer idiom.
1 auto highPriority = static_cast<bool>(features(w)[5]);
Similar problems can arise when you’d want a less precise type than
returned by a function - to save some memory when you know the range of
the possible outputs. Like when a function returns a double, but you want a
float. With auto, there would be no implicit conversion, but you can force
it with the above idiom.
The idiom can also come in handy when you deal with proxy types.
References:
auto variables must be initialized and as such, they are generally immune to
type mismatches that can lead to portability or efficiency problems. auto can
also make refactoring easier, and it typically requires less typing than
explicitly specified types.
auto variables mainly can improve correctness, performance,
maintainability, and robustness. It is also more convenient to type, but that’s
its least important advantage.
If you are worried about readability the previous technique helps, but
otherwise you shouldn’t be troubled, modern IDEs tell you the exact types by
hovering over the variable.
It’s guaranteed that your variable will be initialized. If you forgot, you’ll
get an error from the compiler.
There are no temporary objects, implicit conversion, so it is more
efficient.
Using auto guarantees that you will use the correct type.
In the case of maintenance, refactoring, there is no need to update the
type.
It is the simplest way to portably spell the implementation-specific type
of arithmetic operations on built-in types. Those types might vary from
platform to platform, and it also ensures that you cannot accidentally get
lossy narrowing conversions.
You can omit difficult to spell types, such as lambdas, iterators, etc.
References:
If you put nothing between the curly braces, you’ll get a compilation error as
the compiler is unable to deduce ‘std::initializer_list<auto>’ from ‘<brace-
enclosed initializer list>()’. So you don’t get an empty container, but a
compilation error instead.
If you have at least one element between the braces, the type will be
std::initializer_list.
If you wonder what this type is, you should know that it is a lightweight
proxy object providing access to an array of objects of type const T. It is
automatically constructed when:
References:
Instead of starting with int as a return type, we put the auto keyword at the
beginning of the line and we add int at the end as a return type after an
arrow (->).
With the help of the trailing return type, we can omit the scope of enums for
example.
Instead of writing:
1 Wine::WineType Wine::getWineType() {
2 return _wine_type;
3 }
We can write:
1 auto Wine::getWineType() -> WineType {
2 return _wine_type;
3 }
At the beginning of the line, the compiler cannot know the scope, hence we
have to write Wine::WineType. Whereas when we declare the return type at
the end, the compiler already knows what we are in the scope of Wine, so we
don’t have to repeat that information.
Depending on your scope’s name, you might spare some characters, but at
least you don’t have to duplicate the class name.
References:
References:
C++ Reference
Effective Modern C++ by Scott Meyers
Simplify C++
Question 7: When to use decltype(auto)?
The decltype(auto) idiom was introduced in C++14. Like auto, it deduces
a type from its initializer, but it performs the type deduction using the
decltype rules.
On the other hand, in generic code, you have to be able to perfectly forward a
return type without knowing whether you are dealing with a reference or a
value. decltype(auto) gives you that ability:
1 template<class Fun, class... Args>
2 decltype(auto) foo(Fun fun, Args&&... args) {
3 return fun(std::forward(args)...);
4 }
If you wanted to achieve the same thing simply with auto, you would have to
declare different overloads for the same example function above, one with
the return type auto always deducing to a pure value, and one with auto&
always deducing to a reference type.
It can also be used to declare local variables when you want to apply the
decltype type deduction rules to the initializing expression.
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:
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.
And something very similar for foo. So both of our booleans, a and b are
promoted, they are cast to integers before they are added together.
The keyword static and its
different usages
In this chapter our schwerpunkt, or main point of focus is on the keyword
static.
When we declare a static member in the header file, we’re telling the
compiler about the existence of a static member variable, but we do not
actually define it (it’s pretty much like a forward declaration in that sense).
Because static member variables are not part of class instances (they are
treated similarly to global variables, and get initialized when the program
starts), you must explicitly define the static member outside of the class, in
the global scope.
1 class A {
2 static MyType s_var;
3 };
4
5 MyType A::s_var = value;
Though there are a couple of exceptions. First, when the static member is a
const integral type (which includes char and bool) or a const enum, the
static member can be initialized inside the class definition:
1 class A {
2 static int s_var{42};
3 };
static constexpr members can be initialized inside the class definition
starting from C++17 (no out-of-line initialization required).
1 class A {
2 // this would work for any class supporting consexpr in\
3 itialization
4 static constexpr std::array<int, 3> s_array{ 1, 2, 3 };
5 };
If you are calling a static data member within a member function, the
member function should be declared as static.
It’s been already mentioned but it’s worth emphasizing that static member
variables are created when the program starts and destroyed when the
program ends and as such static members exist even if no objects of the
class have been instantiated.
Reference:
In practice, a static member functions mean that you can call such functions
without having the class instantiated. If you have a static void bar() on
Foo class, you can call it like this Foo::bar() without having Foo ever
instantiated. (You can also call it on an instance by the way: aFoo.bar()).
As this pointer always holds the memory address of the current object and
to call a static member you don’t need an object at all, it cannot have a this
pointer.
Accessing a non-static member function requires that the object has been
constructed but for static calls, we don’t pass any instantiation of the class.
It’s not even guaranteed that any instance has been constructed.
Once again, the const and the const volatile keywords modify whether
and how an object can be modified or not. As there is no object…
References:
If the keyboard is initialized sooner than the logger, there is an issue, as you
can see it in the below example:
1 sdargo@host ~/static_fiasco $ g++ -c Logger.cpp
2 sdargo@host ~/static_fiasco $ g++ -c Keyboard.cpp
3 sdargo@host ~/static_fiasco $ g++ Logger.o Keyboard.o -o \
4 LoggerThenKeyboard
5 sdargo@host ~/static_fiasco $ g++ Keyboard.o Logger.o -o \
6 KeyboardThenLogger
7
8 sdargo@host ~/static_fiasco $ ./KeyboardThenLogger
9
10 theKeyboard: The Keyboard with logger:
11
12 sdargo@host ~/static_fiasco $ ./LoggerThenKeyboard
13 theKeyboard: The Keyboard with logger: aNiceLogger
References:
C++ FAQ
ModernesC++
In case, you have a translation unit A with a static variable sA, which
depends on static variable sB from translation unit B in order to get
initialized, you have 50% chance to fail. This is the static initialization
order fiasco.
Then in the Keyboard.cpp we just have to make sure that we use extern on
the function and we call the function later on instead of referencing the
variable. This works because the local static std::string aLogger
variable will be initialized the first time that theLogger() function is
called. Hence, it’s guaranteed that when theKeyboard is constructed,
theLogger will be initialized.
You might face other issues if theLogger would be used during program 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++
Polymorphism, inheritance and
virtual functions
For the next sixteen questions, we’ll focus on polymorphism, inheritance,
virtual functions and similar topics.
References
The code in the derived class is always called whenever the object is
actually of the derived class, even if the object is accessed by a base class
pointer rather than a derived one.
1 #include <iostream>
2
3 class Car {
4 public:
5 virtual void printName() {
6 std::cout << "Car\n";
7 }
8 };
9
10 class SUV: public Car {public:
11 virtual void printName() override {
12 std::cout << "SUV\n";
13 }
14 };
15
16 int main() {
17 Car* car = new SUV();
18 car->printName();
19 }
20 /*
21 SUV
22 */
A virtual function is a member function that is present in the base class and
might be redefined by the derived class(es). When we use the same function
name both in the base and derived classes, the function in the base class must
be declared with the keyword virtual - otherwise it’s not overriden just
shadowed.
When the function is made virtual, then C++ determines at run-time which
function is to be called based on the type of the object pointed by the base
class pointer. Thus, by making the base class pointer point to different
objects, we can execute different versions of the virtual functions.
In fact, if you want to allow other classes to inherit from a given class, you
should always make the destructor virtual, otherwise, you can easily have
undefined behaviour.
Reference:
Let’s not forget that in C++, methods are non-virtual by default. If we use
override, we might find that there is nothing to override. Without the
override specifier we would just simply create a brand new method. No
more base methods are forgotten to be declared as virtual if you use
consistently the override specifier.
\n
1 class Base {
2 void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // Error: Base::foo is not virtual
7 };
References
References:
As you probably guessed, this technique is useful when you have to deal with
multiple inheritance and you can solve the infamous diamond inheritance.
In practice, virtual base classes are most suitable when the classes that
derive from the virtual base, and especially the virtual base itself, are pure
abstract classes. This means the classes above the “join class” (the one at the
bottom) have very little if any data.
1 TeachingAssistant aTeachingAssistant;
2 Person& student = static_cast<Student&>(aTeachingAssistan\
3 t);
4 Person& worker = static_cast<Worker&>(aTeachingAssistant);
This can be done through vtable pointers. Without going into details, the
object size increases by two pointers, but there is only one Person object
behind and no ambiguity.
You must use the virtual keyword in the middle level of the diamond. Using
it at the bottom doesn’t help.
References:
Troubles with type conversions may also be a source of bugs. You can partly
solve the issues by using the expensive dynamic_cast wherever you used to
use static_cast. Unfortunately, dynamic_cast is much slower, and if you
have to use it too often in your code, it means that your project’s architecture
has quite some room for improvement.
References:
If you copied the above piece of program into a compiler you could easily
get the solution. I hope that first you tried to reason about it.
1 Dog speaks
2 Dog eats 42
3 Animal speaks
4 Animal eats 42
If this is what you expected, congratulations! Most probably then you also
know why.
If you didn’t figure it out and the above solution surprised you, don’t worry.
Check the code once more and read on.
There are two small mistakes in the original code that are so easy to ignore,
and until C++11 you only had your eyes or your code reviewers’ eyes to help
you with.
But since C++11, you have the override specifier. It helps you to explicitly
mark that a function in a derived class is supposed to override something
from its base.
1 std::unique_ptr<Animal> a = std::make_unique<Dog>();
2 a->speak();
When we write such code, we expect a to invoke the behaviour defined in
the derived class, in Dog. Yet we fall back to Animal behaviour.
As the override is missing from the Dog method signatures, let’s add them,
and recompile.
1 #include <iostream>
2 #include <memory>
3
4 class Animal {
5 public:
6 ~Animal() = default;
7 virtual void eat(int quantity) {
8 std::cout << "Animal eats " << quantity << std::endl;
9 }
10
11 void speak() {
12 std::cout << "Animal speaks" << std::endl;
13 }
14 };
15
16 class Dog : public Animal {
17 public:
18 void eat(unsigned int quantity) override {
19 std::cout << "Dog eats " << quantity << std::endl;
20 }
21
22 void speak() override {
23 std::cout << "Dog speaks" << std::endl;
24 }
25 };
26
27 int main() {
28 Dog d;
29 d.speak();
30 d.eat(42u);
31
32 std::unique_ptr<Animal> a = std::make_unique<Dog>();
33 a->speak();
34 a->eat(42u);
35 }
In the first case, it’s because in the base class the parameter is a simple
signed int, while in the derived class we have a function with the same
name, same return type, but with a different parameter: an unsigned int.
We have to match the two, in case we are looking for polymorphic behaviour.
In the other case, we simply forgot to add the virtual keyword to the
signature in the base class, hence it cannot be overridden.
Fix these two mistakes and you’ll get the behaviour that we would commonly
expect.
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 }
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::
This means that all the public and protected variables and functions will be
useable from the derived class even when you use private inheritance.
On the other hand, those public and protected elements of the base class will
not be accessible from the outside through the derived class.
A grandchild of a base class, if its parent inherited privately from the base
(the grandparent…), won’t have any access to the base’s members and
functions. Not even if they were originally protected or even public.
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() {
36 SoDerived* p = new SoDerived();
37 std::cout << p->x() << std::endl;
38 }
39 /*
40 main.cpp: In member function 'virtual int SoDerived::x()':
41 main.cpp:31:12: error: 'class Base Base::Base' is private\
42 within this context
43 31 | return Base::y();
44 | ^~~~
45 main.cpp:19:7: note: declared private here
46 19 | class Derived : private Base {
47 | ^~~~~~~
48 main.cpp:31:19: error: 'Base' is not an accessible base o\
49 f 'SoDerived'
50 31 | return Base::y();
51 | ~~~~~~~^~
52 */
References::
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.
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.
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.
1 Vehicle* p = new Roadster();
So if we want to keep the analogy of cars, we can say that a Car can
privately inherit from the hypothetical Engine class - while it still publicly
inherits from Vehicle. And with this small latter addition of multiple
inheritance, you probably got the point, why composition is easier to
maintain than private inheritance.
That’s exactly what you can find in the C++ Core guidelines.
Use composition when you can, private inheritance when you have to.
According to the Core Guidelines, you have a valid use-case when the
following conditions apply:
References::
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.
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.
References:
Using virtual destructors, you can destroy objects without knowing their
types — the correct destructor for the object is invoked using the virtual
function mechanism. Destructors can also be declared as pure virtual
functions for abstract classes.
But we still don’t know why it’s needed to declare a destructor virtual.
It’s because if you delete an object of a derived class through a pointer to its
base class that has a non-virtual destructor, the behaviour is undefined.
Instead, the base class should destructor should be declared as virtual.
Making base class destructor virtual guarantees that the object of a derived
class is destructed properly, i.e. both base class and derived class
destructors are called. As a guideline, any time you have a virtual function
in a class, you should immediately add a virtual destructor (even if it does
nothing). This way, you ensure against any surprises later.
References:
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.
There are two main arguments. One is that algorithms and containers are
well-separated concerns in the STL. The other one is about the lack of
virtual destructors.
But the lack of virtual destructor doesn’t lead to undefined behaviour 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 to use this
technique. You simply have to respect the limitations, though probably this is
not the best strategy to use in the case of a public library. But more on that
just in a second.
So the other main concern is that you might mix containers and algorithms in
your new object. It’s bad because the creators of the STL said so. And so
what? Alexander Stepanov who originally designed the STL and the others
who have been later contributed to it are smart people and there is a fair
chance that they are better programmers than most of us. They designed
functions, objects that are widely used in the C++ community. I think it’s okay
to say that they are used by everyone.
Most probably we are not working under such constraints, we are not
preparing something for the whole C++ community. We are working on
specific applications with very strict constraints. Our code will not be
reused as such. Never. Most of us don’t work on generic libraries, we work
on one-off business applications.
As long as we keep our code clean (whatever it means), it’s perfectly fine to
provide a non-generic solution.
Reference:
If you look at this function signature, perhaps you think it’s alright:
1 Car::Car(unit32_t horsepower, unit32_t numberOfDoors,
2 bool isAutomatic, bool isElectric);
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.
Now you understand right away which variable represents what. You have to
look a few lines upper to actually get the values, but everything is in sight.
On the other hand, this requires willpower. Discipline. You cannot enforce it.
Well, you can be a thorough code reviewer, but you won’t catch every case
and anyway, you won’t be there all the time.
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:
Fluent Cpp
SandorDargo’s Blog
Correct by Construction: APIs That Are Easy to Use and Hard to
Misuse - Matt Godbolt
As the two subexrepssions are joined with &&, both sides must be true in
order to have the full expression true:
a() b() a() && b()
false false false
false true false
true false false
true true true
In case, a() is not true, the rest of the expression is not evaluated to save
some precious CPU cycles.
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 overloaded function are
evaluated. A function call is a sequence-point 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
On the other hand, a destructor can be virtual, but that’s the topic for
tomorrow.
References:
Why is that?
If a function has two overloaded versions where one is const and the other
is not, the compiler will choose which one to call based on whether the
object is const or not.
On the other hand, it has nothing to do with the constness of the variable
returned.
This was just a simple introduction to the coming days when we are going to
talk about constness, const correctness and how to use the const keyword.
Reference:
You have a function taking integer numbers. Whole numbers. Let’s say it
takes as a parameter how many people can sit in a car. It might be 2, there are
some strange three-seaters, for some luxury cars it’s 4 and for the vast
majority, it’s 5. It’s not 4.9. It’s not 5.1 or not even 5 and a half. It’s 5. We
don’t traffic body parts.
How can you enforce that you only receive whole numbers as a parameter?
Great!
You might say that this is fine and in certain situations it probably is. But in
others, this behaviour is simply not acceptable.
But = delete can be used for more. It can be applied to any function, should
they be members or free.
That’s it. By deleting some overloads of a function, you can forbid implicit
conversions from certain types to the types you expect. Now, you are in
complete control of what kind of parameters your users can pass to your API.
Reference:
Sandor Dargo’s blog: Three ways to use the = delete specifier in C++
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.
It means, that the lambda is not even stored and denoted by a variable, but
where you create it, you invoke it right away.
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.
1 auto fortyTwo = [](){return 42;}(); // those parantheses \
2 at the end invoke the lambda expression!
In most cases, it’s very easy, you just put const next to the type and initialize
your variable right on the spot. But you might run into situations where you
have different possible values at your hands.
Easy peasy.
This way, you can perform complex initializations of const variables, yet
you don’t have to find a proper place nor a name for the initialization logic.
References:
The current object (*this) can be implicitly captured if either of the capture
defaults is present. If implicitly captured, it is always captured by reference,
even if the capture default is =. However since C++20 the implicit capture of
*this with the capture default = is deprecated.
References:
The output is going to be 1. If a would have been declared as const and b not,
the result would be 2. In fact, the result doesn’t depend on b’s constness.
Why is that?
If a function has two overloaded versions where one is const and 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:
In real life, I find that we tend to forget the value making variables const,
even though there are good examples at conference talks and it really has no
bad effect on your code, on maintainability.
This is such an important idea that in Rust, all your variables are declared as
const unless you say they should be mutable.
Declare your local variables const if you don’t plan to modify them.
Regarding global variables, well, avoid using them, but if you do, also make
them const whenever possible.
Reference:
CppCon 2016: Jason Turner “Rich Code for Tiny Computers: A Simple
Commodore 64 Game in C++17”
Sandor Dargo’s Blog: When to use const in C++? Part I: functions and
local variables
Because you might want to signal that they are immutable, that they should
never change.
The first is that classes with const members are not assignable:
1 class MyClassWithConstMember {
2 public:
3 MyClassWithConstMember(int a) : m_a(a) {}
4
5 private:
6 const int m_a;
7 };
8
9 int main() {
10 MyClassWithConstMember o1{666};
11 MyClassWithConstMember o2{42};
12 o1 = o2;
13 }
14 /*
15 main.cpp: In function 'int main()':
16 main.cpp:12:8: error: use of deleted function 'MyClassWit\
17 hConstMember& MyClassWithConstMember::operator=(const MyC\
18 lassWithConstMember&)'
19 12 | o1 = o2;
20 | ^~
21 main.cpp:1:7: note: 'MyClassWithConstMember& MyClassWithC\
22 onstMember::operator=(const MyClassWithConstMember&)' is \
23 implicitly deleted because the default definition would b\
24 e ill-formed:
25 1 | class MyClassWithConstMember {
26 | ^~~~~~~~~~~~~~~~~~~~~~
27 main.cpp:1:7: error: non-static const member 'const int M\
28 yClassWithConstMember::m_a', cannot use default assignmen\
29 t operator
30 */
If you think about it, it makes perfect sense. A variable is something you
cannot change after initialization. And when you want to assign a new value
to an object, thus to its members, it’s not possible anymore.
As such it also makes it impossible to use move semantics, for the same
reason.
From the error messages, you can see that the corresponding special
functions, such as the assignment operator or the move assignment operator
were deleted. Which means we have to implement them by hand. Don’t forget
about the rule of 5. If we implement one, we have to implement all the 5.
Let’s take the assignment operator as an example. What should we do with it?
You have your const member, fine. You have the assignment working, fine.
Then if anyone comes later and wants to do the same “magic” outside of the
special functions, for sure, it would be a red flag in a code review.
We’ve just had a look at the copy assignment and it wouldn’t work without
risking undefined behaviour.
References:
Why is that?
Putting const somewhere shows the reader (and the compiler of course) that
something should not be modified. When we return something by value it
means that a copy will be made for the caller. Okay, you might have heard
about copy elision and its special form, return value optimization (RVO), but
essentially we are still on the same page. The caller gets his own copy.
Imagine that you buy a house but you cannot modify it? While there can be
special cases, in general, you want your house to be your castle. Similarly,
you want your copy to really be your object and you want to be able to do
with it just whatever as an owner of it.
It doesn’t make sense and it’s misleading to return by value a const object.
Following up with the debugger we can see that we didn’t benefit from a
move, but actually, a copy was made.
Please note that there are important books advocating for returning user-
defined types by const value. They were right in their own age, but since then
C++ went through a lot of changes and this piece of advice became obsolete.
References:
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?
The function is constant, and the returned pointer is constant but the data we
point at can be modified. However, I see no point in returning a const
pointer because the ultimate function call will be an rvalue, and rvalues of
non-class type cannot be const, meaning that const will be ignored anyway.
Reference:
Sandor Dargo’s Blog: When to use const in C++? Part III: return types
It’s worth noting that outside of f() we wouldn’t be able to use aRef as the
instance of MyObject gets destroyed at the end of the function f().
References:
As always, it depends.
If we don’t plan to modify their value, yes we should. For better readability,
for the compiler and for the future.
1 void setFoo(const int foo) {
2 this->m_foo = foo;
3 }
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.
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.
So we don’t take fundamental data types by const& and we only mark them
const when we don’t want to modify them.
Reference:
Sandor Dargo’s Blog: When to use const in C++? Part IV: parameters
If we’d take a class by value as a parameter, it’d mean that we would make a
copy of them. In general, copying an object is more expensive than just
passing a reference around.
Obviously, if you want to modify the original object, then you only take it by
reference and omit the const.
You might take an object by value if you know you’d have to make a copy of
it.
1 void doSomething(const ClassA& foo) {
2 // ...
3 ClassA foo2 = foo;
4 foo2.modify();
5 // ...
6 }
In that case, just simply take it by value. We can spare the cost of passing
around a reference and the mental cost of declaring another variable and
calling the copy constructor.
Although it’s worth noting that if you are accustomed to taking objects by
const& you might have done some extra thinking whether passing by value
was on purpose or by mistake.
You should also note that there are objects where making the copy is less
expensive or similar to the cost of passing a reference. It’s the case for Small
String Optimization or for std::string_view. This is beyond the scope of
today’s lesson.
For objects, we can say that by default we should take them by const
reference and if we plan to locally modify them, then we can consider
taking them by value. But never by const value, which would force a copy
but not let us modify the object.
Reference:
Sandor Dargo’s Blog: When to use const in C++? Part IV: parameters
The const in the function declaration would imply that it won’t modify what
you pass in. But it can. At the same time, it’s not a big deal, as it will only
modify its own copy, not the original variable.
Please note that as soon as we turn the parameter into a reference in both the
declaration and the definition, the code stops compiling.
1 #include <iostream>
2
3 class MyClass {
4 public:
5 void f(const int&);
6 };
7
8 void MyClass::f(int& a) {
9 a = 42;
10 std::cout << a << std::endl;
11 }
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:
References:
So what is an aggregate?
It’s either an array type or a class type with some restrictions that you can
read here.
In its simplest form, it looks exactly like normal constructor calls with
parentheses, but this time with braces.
1 std::string text{"abcefg"};
2
3 Point a{5,4,3}; //Point is class taking 3 integers as par\
4 ameters
The following examples show the problem with the classical initialization
for numerical types. It doesn’t matter whether we use direct initialization or
assignment.
1 #include <iostream>
2
3 int main() {
4 int i1(3.14);
5 int i2=3.14;
6 std::cout << "i1: " << i1 << std::endl;
7 // i1: 3
8 std::cout << "i2: " << i2 << std::endl;
9 // i2: 3
10 int i3{3.14} // error: narrowing conversion of '3.14000\
11 000000000
12 unsigned int i4{-41}; // error: narrowing conversion of\
13 '-41'
14 }
As we can see, with brace initialization the compiler informs us about the
narrowing in front of a nice error.
Reference:
The argument is not a WrappedInt object, just a plain old int. However,
there exists a constructor for WrappedInt that takes an int so this
constructor can be used to convert the parameter to the correct type.
Prefixing the explicit keyword to the constructor prevents the compiler from
using that constructor for implicit conversions. Adding it to the above class
will create a compiler error at the function call doSomething(42). It is now
necessary to call for conversion explicitly with
doSomething(WrappedInt(42)).
The reason you might want to do this is to avoid accidental construction that
can hide bugs. Let’s think about strings. You have a MySpecialString class
with a constructor taking an int. This constructor creates a string with the
size of the passed in int. You have a function print(const
MySpecialString&), and you call print(3) (when you actually intended to
call print(“3”)). You expect it to print “3”, but it prints an empty string of
length 3 instead.
To avoid such subtle bugs, one should always consider making single
argument constructors explicit initially (more or less automatically), and
removing the explicit keyword only when the implicit conversion is wanted
by design.
Reference:
But what is probably more interesting is how it can help readability and
safety when you use strong types.
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 hardcoded
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};
This has the following implication. In case you have a function with three
overloads, including integral types and pointers, you might get some
surprises:
1 #include <iostream>
2
3 void foo(int) {
4 std::cout << "foo(int) is called" << std::endl;
5 }
6
7 void foo(bool) {
8 std::cout << "foo(bool) is called" << std::endl;
9 }
10
11 void foo(void*) {
12 std::cout << "foo(void*) is called" << std::endl;
13 }
14
15 int main() {
16 foo(0); // calls foo(int), not foo(void*)
17 foo(NULL); // might not compile, but
18 // typically calls foo(int), never foo(void*)
19 }
Hence was created the guideline to avoid overloading on the pointer and
integral types.
On the other hand, nullptr doesn’t have an integral type. It’s type is
std::nullptr_t. It is a distinct type that is not itself a pointer type or a
pointer to member type. At the same time, it implicitly converts to all raw
pointer types, and that’s what makes nullptr act as if it were a pointer of all
types.
References:
C++ Reference
Effective Modern C++ by Scott Meyers
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.
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:
The modern scoped enums use the class keyword and the enumerators are
visible only within the enum. They convert to other types only with a cast.
They are usually called either scoped enums or enum classes.
1 enum class Transmission { manual, automatic };
2
3 // now compiles, manual has not been declared
4 auto manual = false;
5
6 // error, no enumerator
7 // manual without the enum scope is just a bool
8 Transmission t = manual;
9
10 Transmission t = Transmission::manual; // OK
11 auto t = Transmission::manual; // OK
Both scoped and unscoped enums support the specification of the underlying
type. The default underlying type for scoped enums is int. Unscoped enums
have no default underlying type.
References:
C++ Reference
Effective Modern C++ by Scott Meyers
Before C++11 there was no other option than declaring the unneeded special
functions private and not implementing them. As such one could disallow
copying objects (there was no move semantics 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 };
it’s more explicit than having the functions in the private section which
might only be a mistake
in case you try to make a copy, you’ll already get an error at
compilation time
References:
C++ Reference
Effective Modern C++ by Scott Meyers
You have a function taking integer numbers. Whole numbers. Let’s say it
takes as a parameter how many people can sit in a car. It might be 2, there are
some strange three-seaters, for some luxury cars it’s 4 and for the vast
majority, it’s 5. It’s not 4.9. It’s not 5.1 or not even 5 and a half. It’s 5. We
don’t traffic body parts.
How can you enforce that you only receive whole numbers as a parameter?
Great!
You might say that this is fine and in certain situations it probably is. But in
others, this behaviour is simply not acceptable.
Since C++11, we can use the delete specifier in order to restrict certain types
for copying of moving, and in fact, even from polymorphic usage.
But = delete can be used for more. It can be applied to any function, should
they be members or free.
That’s it. By deleting some overloads of a function, you can forbid implicit
conversions from certain types to the types you expect. Now, you are in
complete control of what kind of parameters your users can pass to your API.
Reference:
Trivial types have a trivial a default constructor, trivial copy and move
constructors, trivial copy and move assignment operators and a trivial
destructor. In each case, trivial means the constructor/operator/destructor is
not user-provided and belongs to a class that has
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:
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
36 false
37 */
References:
C++ Reference
Microsoft Docs
Smart pointers
During the the next couple of questions, we’ll focus on what smart pointers
are, when and why should we use them.
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.
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.
1 SomeLimitedResource* resource = new SomeLimitedResource();
2 resouce->doIt(); // oops, it throws an exception...
3
4 delete resource; // this never gets called, so we have a \
5 leak
References:
They have the same size as a raw pointer, and for most operations, they
require the same amount of instructions.
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 example
Effective Modern C++ by Scott Meyers
It’s available since C++11 and there are two ways to initalize a shared
pointer:
1 std::shared_ptr<T> ptr (new T());
2 std::shared_ptr<T> ptr2 = std::make_shared<T>();
References:
C++ Reference
Effective Modern C++ by Scott Meyers
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.
1 std::shared_ptr<T> sp = wp.lock();
2 std::shared_ptr<T> sp2(wp);
It can be useful for cyclic ownerships, to break the cycle. Let’s say that you
have an instance of a Keyboard, a Logger, and a Screen object. Both the
Screen and the Keyboard has a shared pointer of the Logger and the Logger
should have a pointer to the Screen.
What pointer can it use? If we use a raw pointer when the Screen gets
destroyed, the Logger will be still alive and the Keyboard still has shared
ownership on it. The Logger has a dangling pointer to the Screen.
Otherwise, it can be useful for caching and for the observer pattern. Check
out the referenced book to get more details.
Reference:
Compared to the direct use of new, make functions eliminate source code
duplication, improve exception safety, and std::make_shared generates
code that’s smaller and faster.
1 auto ptr = std::make_shared<T>(); // Prefer this
2 std::shared_ptr<T> ptr2(new T);
As you can see, with make functions, we have to type the type name only
once, we don’t have to duplicate it.
References:
Taking smart pointers in such cases will only make its API more restrictive
and the run-time cost higher.
At the same time, we should refrain from using the new keyword. new almost
always means that you should delete somewhere. Unless you are creating a
std::unique_ptr in C++11 where std::make_unique is not available.
To summarize, try not to use new. When it comes to ownership use smart
pointers and the related factory functions - unless some special cases apply
(check Effective Modern C++: Item 21). Otherwise, if you don’t want to
share or transfer ownership, just use a raw pointer. It’ll keep the API more
user-friendly and the run-time performance faster.
References:
Core Guidelines: F7
Effective Modern C++ by Scott Meyers (Item 21)
Question 57: When and why should we
initialize pointers to nullptr?
If it’s sure that the pointer will get initialized, it doesn’t make sense to pre-
initialize with a nullptr. Approaching from the other direction, you should
initially assign nullptr to a variable in case it’s not sure that you’d
initialize it otherwise.
For example, the following piece of code might emit a warning based on
your compiler and its settings. And hopefully, you treat warnings errors:
1 T* fun() {
2 T* t;
3 if (someCondition()) {
4 t = getT();
5 }
6 return t;
7 }
8 /*
9 Warnings by Clang:
10 prog.cc:22:6: warning: variable 't' is used uninitialized\
11 whenever 'if' condition is false [-Wsometimes-uninitiali\
12 zed]
13 if (someCondition()) {
14 ^~~~~~~~~~~~~~~
15 prog.cc:25:9: note: uninitialized use occurs here
16 return t;
17 ^
18 prog.cc:22:2: note: remove the 'if' if its condition is a\
19 lways true
20 if (someCondition()) {
21 ^~~~~~~~~~~~~~~~~~~~~
22 prog.cc:21:6: note: initialize the variable 't' to silenc\
23 e this warning
24 T* t;
25 ^
26 = nullptr
27 */
When you don’t initialize a pointer and then try to use it, you have 3 possible
problems:
The pointer might point at a memory location that you don’t have access
to, in such cases it causes a segmentation fault and your program crashes
The pointer - accidentally - might point at some real data, and if you
don’t know what it’s pointing at, you might cause unpredictable (and
very hard to debug) changes to your data.
You have no way to determine if the pointer has been initialized or not.
How would you tell the difference between a valid address and the
address that just happened to be there when you declared the pointer?
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.
1 T* fun() {
2 T* t = nullptr;
3 if (someCondition()) {
4 t = getT();
5 }
6 return t; // No warnings anymore!
7 }
It’s a matter of style, but I’d personally prefer to avoid having to initialize a
pointer as nullptr. In those cases, I’d prefer to return a nullptr right away
and otherwise just initialize the pointer with the correct value at declaration
time.
1 T* fun() {
2 if (!someCondition()) {
3 return nullptr;
4 }
5 return getT();
6 }
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:
One forwards lvalues as lvalues and rvalues as rvalues, while the other is a
conditional cast. It forwards rvalues as rvalues and prohibits forwarding of
rvalues as lvalues. Attempting to forward an rvalue as an lvalue, is a
compile-time error.
References:
If the form of the type declaration isn’t precisely type&&, or if type deduction
does not occur - there is no auto used - we have an rvalue reference.
1 void f(MyClass&& param); // rvalue reference
2
3 MyClass&& var1 = MyClass(); // rvalue reference
4
5 template<typename T>
6 void f(std::vector<T>&& param); // rvalue reference
References:
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.
You are not allowed to declare a reference to a reference, but compilers may
produce them in the above-listed contexts. When compilers generate
references to references, reference collapsing dictates what happens next.
1 int x;
2 auto& & rx = x; // error! can't declare reference to refe\
3 rence
4
5 typedef int& T;
6 // a has the type int&
7
8 T&& a; // (&& + & => &)
9 template <typename T> void func(T&& a);
10 auto fp = func<int&&>; // && of func + && of int => &&
There are two kinds of references (lvalue and rvalue), so there are four
possible reference-reference combinations:
lvalue to lvalue
lvalue to rvalue
rvalue to lvalue
rvalue to rvalue
References:
In case a constant is needed, but you provide only a runtime function, the
compiler will let you know.
It’s not a good idea to make all functions constexpr as most computations
are best done at run time. At the same time, it’s worth noting that constexpr
functions will be always threadsafe and inlined.
References:
Otherwise, you should use noexcept for functions that don’t throw an
exception, or if it throws, then you don’t mind letting the program crash.
Here is a small code sample from ModernesC++ to show how to use it:
1 void func1() noexcept; // does not throw
2 void func2() noexcept(true); // does not throw
3 void func3() throw(); // does not throw
4 void func4() noexcept(false); // may throw
But what does it mean that a function doesn’t throw an exception? It means it
cannot use any other function that throws, it is declared as noexcept itself
and it doesn’t use dynamic_cast to a reference type.
So you can use noexcept when it’s better to crash than actually handling an
exception, as the Core Guidelines also indicates.
Using noexcept can give hints both for the compiler to perform certain
optimizations and for the developers as well that they don’t have to handle
possible exceptions.
With the next few questions, we’ll discover the main features of C++20. Stay
tuned!
References:
References:
A simple one looks like this: [[attribute]]. But it can have parameters
([[deprecated("because")]]) or a namespace ([[gnu::unused]]) or
both.
Reference:
C++ Reference
It helps to decide which operand is greater, lesser or whether they are equal.
With C++20, you can simply =default the spaceship operator and it will
generate all the six constexpr and noexcept operators for you and they
perform lexicographical comparison.
References:
ModernesC++
CppReference
References:
Hence a simple hello world program can grow from around 100 bytes up to
13,000. Simply because of the inclusion of <iostream>.
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.
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
Microsoft Devblogs
ModernesC++
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).
In case you have a class holding on a raw or smart pointer, a file descriptor,
database connection, or other types managing resources, there is a fair
chance that the generated functions would be incorrect and you should
implement them by hand.
And here comes the rule of three, if you implement by hand either the copy
constructor, the copy assignment operator or the destructor, the compiler will
not generate those that you didn’t write. So if you need to write any of these
three, the assumption is that you’ll need the rest as well.
This is the rule of three introduced by C++98 and tomorrow will move on to
the rule of 5.
References:
C++ Reference
Fluent C++
Modernes C++
Let’s recap.
If you implement by hand either the copy constructor, the copy assignment
operator or the destructor, the compiler will not generate those that you
didn’t write. It’s because the compiler assumes that if you’d have to
implement one of them, it’s almost for sure that you’d need the others too.
The rule of three was introduced by C++98, and the rule of five was
introduced by C++11.
References:
C++ Reference
Fluent C++
Modernes C++
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:
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:
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:
It is generated by the compiler by default, but you have to pay attention as the
rule of 5 applies. If any of the other 4 special functions is implemented
manually, the destructor will not be generated. As a quick reminder, the
special functions besides the destructor are:
copy constructor
assignment operator
move constructor
move assignment
On the other hand, a destructor can be virtual, but that’s the topic for
tomorrow.
References:
You might not want a class to be copied or moved, so you want to keep
related special functions unreachable for the caller. One option is to declare
them as private or protected and the other is that you explicitly delete them.
1 class NonCopyable {
2 public:
3 NonCopyable() {/*...*/}
4 // ...
5
6 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 unneeded 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 };
it’s more explicit than having the functions in the private section which
might only be an error
in case you try to make a copy, you’ll already get an error at
compilation time
References:
C++ Reference
Effective Modern C++ by Scott Meyers
Trivial types have a trivial default constructor, trivial copy and move
constructor, trivial copy and move assignment operator and trivial destructor.
In each case, trivial means the constructor/operator/destructor is not user-
provided and belongs to a class that has
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
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
It’s true that it’s simple to create an object without passing any parameters,
but it’s only useful if the created object is fully usable. If it still has to be
initialized, then the simple creation is worth nothing and in fact, it’s even
misleading and harmful.
On the other hand, many features of the standard library require having a
default constructor.
Having a default constructor also helps to define how an object should look
like that was just moved away from.
It’s worth noting that having a default constructor doesn’t mean that you
should define it. Whenever possible, let the compiler generate it. So for
example, if a default constructor only default initializes data members, then
you are better off using in-class member initializers instead and let the
compiler generate the default constructor.
In the next three weeks, we’ll discover some parts of object-oriented design,
inheritance, how C++ handles polymorphism, the Curiously Recurring
Template Pattern, etc. Stay tuned!
References:
On the contrary, in C++ structs can have both functions, constructors, even
virtual functions. They can use inheritance, they can be templated, exactly
like a class.
When we speak about default visibility, it’s worth mentioning that it applies
to inheritance as well. When you write class Derived : Base ... you
get private inheritance, on the other hand, when you write struct Derived:
Base ... what you have is public inheritance.
As we see, there is not much technical difference between the two, but on the
other hand according to the established conventions what we express with
one and the other is quite different.
There are the following Core guidelines discussing this topic more in detail:
In the above example, we can see that the default constructor simply calls the
constructor that takes 2 arguments. This becomes practical when you have
more members and you have to add one more. You have to update the
constructor that directly initializes all the members, and the compiler will
gently remind you of all the places where you have to pass in the extra
variable.
Question 79: Explain the concept of
covariant return types and show a use-case
where it comes in handy!
Using covariant return types for a virtual function and for all its overridden
versions means that you can replace the original return type with something
narrower, in other words, with something more specialized.
It tells the reader that “this is a virtual method, that is overriding a virtual
method of the base class.”
Let’s not forget that in C++, methods are non-virtual by default. If we use
override, we might find that there is nothing to override. Without the
override specifier we would just simply create a brand new method. No
more base methods are forgotten to be declared as virtual if you use
consistently the override specifier.
1 class Base {
2 void foo();
3 };
4
5 class Derived : Base {
6 void foo() override; // Error: Base::foo is not virtual
7 };
Unless you can tweak the design to remove this need, the solution is a friend
class, which is capable of accessing the protected as well as the private
members of the class in which it is declared as a friend.
1 #include <iostream>
2
3 class A {
4 public:
5 void foo() {
6 std::cout << "foo\n";
7 }
8
9 private:
10 void bar() {
11 std::cout << "bar\n";
12 }
13
14 friend class B;
15 };
16
17 class B {
18 public:
19 void doIt() {
20 A a;
21 a.foo();
22 a.bar(); // B is a friend class of A, so it has access\
23 to A::b
24 }
25 }
26
27 int main() {
28 A a;
29 a.foo();
30 // a.bar();
31 // this would fail to compile as A::bar is private
32 B b;
33 b.doIt()
34 }
Similarly to the friend class, a friend function is able to access private and
protected class members. A friend function can either be a free function or a
method of a class.
1 #include <iostream>
2
3 class A {
4 public:
5 void foo() {
6 std::cout << "foo\n";
7 }
8
9 private:
10 void bar() {
11 std::cout << "bar\n";
12 }
13
14 friend void freeFunction();
15 };
16
17 void freeFunction() {
18 A a;
19 a.foo();
20 a.bar(); // freeFunction is a friend function of A, so \
21 it has access to freeFunction
22 }
23
24 int main() {
25 A a;
26 a.foo();
27 // a.bar(); // this would fail to compiler as A::bar is\
28 private
29 freeFunction();
30 }
Here is an example:
1 int calculateArea(int a, int b);
2 int calculateArea(int a, int b=2);
3 int calculateArea(int a=3, int b=2);
These default values are used if one or more arguments are left blank while
calling the function - according to the number of left blank arguments.
Let’s see a complete example. If the value is not passed for any of the
parameters during the function call, then the compiler uses the default
value(s) provided. If a value is specified, then the default value is stepped on
and the passed value is used.
1 #include <iostream>
2
3 int calculateArea(int a=3, int b=2) {
4 return a*b;
5 }
6
7 int main() {
8 std::cout << calculateArea(6, 4) << '\n';
9 std::cout << calculateArea(6) << '\n';
10 std::cout << calculateArea() << '\n';
11 }
12 /*
13 24
14 12
15 6
16 */
This means that that the int calculateArea(int a=5, int b); would
not compile. On the other hand, int g(int n = 0, ...) would compile as
ellipsis does not count as a parameter.
One last thing to note is that you should always declare your defaults in the
header, not in the cpp file.
Ideally, the delete operator should not be used for this pointer. However, if
it is used, then you must take into account the following points.
delete operator works only for objects allocated on the heap (using
operator new), if it’s allocated on the stack the behaviour is undefined,
your application might crash.
1 class A {
2 public:
3 void fun() {
4 delete this;
5 }
6 };
7
8 int main() {
9 A *aPtr = new A;
10 aPtr->fun(); // a valid call
11
12 // make ptr NULL to make sure that things are not acces\
13 sed using ptr.
14 aPtr = nullptr;
15
16 A a;
17 a.fun(); // undefined behaviour
18 }
Once delete this is done, any member of the deleted object should
not be accessed after deletion.
1 #include <iostream>
2
3 class A {
4 public:
5 void fun() {
6 delete this;
7 // undefined behaviour, your application might crash
8 std::cout << x << '\n';
9 }
10 private:
11 int x{0};
12 };
13
14 int main() {
15 A a;
16 a.fun();
17 }
The best thing is to not do delete this at all because it is easy to accidentally
access member variables after the deletion. Besides, the caller might not
realize your object has self-destructed.
Also, delete this is a “code smell” indicating that your code might not
have an asymmetric strategy for object ownership (who allocates and who
deletes).
As you probably guessed, this technique is useful when you have to deal with
multiple inheritance and you can solve the infamous diamond inheritance.
In practice, virtual base classes are most suitable when the classes that
derive from the virtual base, and especially the virtual base itself, are pure
abstract classes. This means the classes above the “join class” (the one in the
bottom) have very little if any data.
This can be done through vtable pointers. Without going into details, the
object size increases by two pointers, but there is only one Person object
behind and no ambiguity.
You must use the virtual keyword in the middle level of the diamond. Using
it at the bottom doesn’t help.
You can find more detail at the Core Guidelines\nAddition reference here.
Troubles with type conversions may also be a source of bugs. You can partly
solve the issues by using the expensive dynamic_cast wherever you used to
use static_cast. Unfortunately, dynamic_cast is much slower, and if you
have to use it too often in your code, it means that your project’s architecture
has quite some room for improvement.
If you look at this function signature, perhaps you think it’s alright:
1 Car::Car(unit32_t horsepower, unit32_t numberOfDoors,
2 bool isAutomatic, bool isElectric);
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.
Now you understand right away which variable represents what. You have to
look a few lines upper to actually get the values, but everything is in sight.
On the other hand, this requires willpower. Discipline. You cannot enforce it.
Well, you can be a thorough code reviewer, but you won’t catch every case
and anyway, you won’t be there all the time.
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:
Fluent Cpp
SandorDargo’s Blog
Correct by Construction: APIs That Are Easy to Use and Hard to
Misuse - Matt Godbolt
But what is probably more interesting is how it can help readability and
safety when you use strong types.
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 hardcoded
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};
References:
C++ Reference
Modernes C++
In the implementation of this function, it will be all fine, you might have
something like this somewhere in it:
1 //...
2 if (buy) {
3 // do buy
4 // ...
5 } else {
6 // do sell
7 // ...
8 }
And even if you look it up, it’s so easy to overlook and mix up true with
false.
Reference:
The big difference is that a shallow copy may not do what you want for
members that are not storing values, but pointers to values. In other words,
for members pointing to dynamically allocated memory. A shallow copy of
those means that no new memory will be allocated and in fact, if you modify
such a member through one object, the changes will be visible from all the
copies.
The default copy constructor and assignment operator make shallow copies.
In case, a class has a reference or pointer members, there is a fair chance that
you have written the copy constructor yourself and you should also write the
assignment operator.
If you have to do so, don’t forget about the rule of five. Implementing one of
the special functions (such as the just mentioned copy constructor or the
assignment operator) will likely make you implement all the other special
functions.
References:
Fluent C++
Fredosaurus.com
LearnCpp
Tutorials Point
Question 91: Are class functions taken into
consideration as part of the object size?
No, only the class member variables determine the size of the respective
class object. The size of an object is at least the sum of the sizes of its data
members. It will never be smaller than this.
The compiler might insert padding between data members to ensure that each
data member meets the alignment requirements of the platform. Some
platforms are very strict about alignment, more than the others, yet in general,
code with proper alignment will perform significantly better. Therefore the
optimization setting might affect the object size.
Static members are not part of the class object and as such, they are not
included in the object’s layout, they don’t add up to the object’s size.
In practice, dynamic dispatch usually means the action of finding the right
function to call. In the general case, when you define a method inside a class,
the compiler will remember its definition and execute it every time a call to
that method is encountered.
1 #include <iostream>
2
3 class A {
4 public:
5 virtual void foo() {
6 std::cout << "This is A::foo()" << '\n';
7 }
8 };
9
10 class B : public A {
11 public:
12 void foo() override {
13 std::cout << "This is B::foo()" << '\n';
14 }
15 }
16
17 int main() {
18 A* a = new B();
19 a->foo();
20 }
If we used static dispatch the call a->foo() would execute A::foo(), since
(from the point of view of the compiler) a points to an object of type A. This
would be wrong because a actually points to an object of type B and B::bar()
should be called instead.
Virtual tables and pointers are means to implement dynamic dispatch. For
every class that contains virtual functions, the compiler constructs a virtual
table (vtable) serving as a lookup table of functions used to resolve function
calls in a dynamic binding manner. The vtable contains an entry for each
virtual function accessible by the class and stores a pointer to its definition.
Only the most specific function definition callable by the class is stored in
the vtable. Entries in the vtable can point either to virtual functions declared
in the class itself or to virtual functions inherited from a base class.
Every time the compiler creates a vtable for a class, it adds an extra
argument to it: a pointer to the corresponding virtual table called the pointer.
The pointer is just another class member added by the compiler. It increases
the size of every object - where the class has a vtable - by the size of
vpointer. This means that while there is one vtable per class, there is one
vpointer per object.
When a virtual function is called, the vpointer of the object is used to find
the corresponding vtable of the class. Next, the function name is used as an
index to the vtable to find the most specific function to be executed.
References:
While the usual answer is “yes, of course”, it’s not the correct answer. If you
just think about the standard library itself, many classes don’t have a virtual
destructor. That’s something I wrote about in strongly-types containers. So if
for example std::vector doesn’t have a virtual destructor, the answer “of
course”, cannot be correct.
Any operation that will be performed through the base class interface, and
that should behave virtually, should be virtual. If deletion, therefore, can be
performed polymorphically through the base class interface, then it must
behave virtually and must be virtual. The language requires it - if you delete
polymorphically without a virtual destructor, you have to face something that
is called undefined behaviour. Something that we always want to avoid.
1 class Base { /*...*/ };
2
3 class Derived : public Base { /*...*/ };
4
5 int main() {
6 Base* b = new Derived;
7 delete b; // Base::~Base() had better be virtual!
8 }
But base classes need not always allow polymorphic deletion. In such cases,
make base class destructor protected and non-virtual. It’ll make deletes
through base class pointers fail:
1 class Base {
2 /*...*/
3 protected:
4 ~Base() {}
5 };
6
7 class Derived : public Base { /*...*/ };
8
9 int main() {
10 Base* b = new Derived;
11 delete b; // error, illegal
12 }
13 /*
14 main.cpp: In function 'int main()':
15 main.cpp:11:10: error: 'Base::~Base()' is protected withi\
16 n this context
17 11 | delete b; // error, illegal
18 | ^
19 main.cpp:4:3: note: declared protected here
20 4 | ~Base() {}
21 | ^
22 */
References:
Any class that is meant to be instantiated and inherits from an abstract one, it
must implement all the pure virtual - in other words, abstract - functions.
Though non-leaf classes inheriting from an abstract class that are only used
as base classes of other classes, they don’t have to implement the pure virtual
functions:
1 class AbstractClass {
2 public:
3 virtual void doIt() = 0;
4 };
5
6 class StillAbstractClass : public AbstractClass {
7 public:
8 // no need to implement doIt, we can even add more pure\
9 virtuals
10 virtual void foo() = 0;
11 };
12
13 class Leaf : public StillAbstractClass {
14 void doIt() override { /* ... */ }
15 void foo() override {/* ... */ }
16 };
How?
That’s an exciting question! By inheriting from a class that takes the inheriting
class as a template parameter. It’s called the “Curiously Recurring Template
Pattern” or “Upside Down Inheritance”. When in Microsoft Christian
Beaumont saw it in a code review, he thought it cannot possibly compile, but
it worked and it became widely used in some Windows libraries.
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.
Note that contrary to typical casts to the derived class, we don’t use
dynamic_cast here. A dynamic_cast is used when you want to make sure
at run-time that the derived class you are casting into is the correct one. But
here we don’t need this guarantee: the Base class is designed to be inherited
from by its template parameter, and by nothing else. Therefore it takes this as
an assumption, and a static_cast is enough.
References:
Fluent C++
Wikipedia
Question 97: How would you add
functionality to your classes with the
Curiously Recurring Template Pattern
(CRTP)?
The CRTP consists of:
As mentioned yesterday, the main advantage of this technique is that the base
class have access to the derived class methods. Why is that?
In the base class, we can get the underlying Derived object with a static cast:
1 class Base {
2 void foo() {
3 X& underlying = static_cast<X&>(*this);
4 // now you can access X's public interface
5 }
6 };
You can check the below links for additional practical uses.
Resources:
Fluent C++
Sandor Dargo’s blog
When an object is created, the user can rightly expect to have a fully usable,
fully initialized object. If the user still has to call init(), that’s not the case.
On the contrary, no destroy(), close() or alike function should be called
by the user when he stops using the object. An object should take care of
releasing whatever resources it acquired during its initialization. This
concept is also known as RAII (Resource Acquisition Is Initialization).
To conclude, don’t use init() functions. An object should take care of itself
through its constructor and destructor. If the construction is not possible like
that, encapsulate it by a factory function.
References:
The C++ standard precisely defines the observable behavior of every C++
program that does not fall into one of the following classes:
ill-formed
ill-formed, no diagnostic required
implementation-defined behaviour
unspecified behaviour
undefined behaviour
References:
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.
These semantic errors are either detectable at link time, or if the program is
executed, it results in undefined behaviour.
Such problems are violations against the One Definition Rule which says that
only one definition of any variable, function, class type, enumeration type,
concept (since C++20) or template is allowed in any one translation unit.
There might be multiple declarations, but only one definition. Check out the
references for more details on ODR.
References:
Even with the implementation, the compiler doesn’t have to document how it
resolves such situations.
This means that different result sets can be completely valid depending on
which implementation you are using.
But whatever will happen it’s guaranteed that the effects of the unspecified
code are strictly limited to the commands affected. It’s not the case for
undefined behaviour when the compiler is free to remove complete execution
branches.
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
1 #include <iostream>
2
3 int x = 333;
4
5 int add(int i, int j) {
6 return i + j;
7 }
8
9 int left() {
10 x = 100;
11 return x;
12 }
13
14 int right() {
15 ++x;
16 return x;
17 }
18
19 int main() {
20 std::cout << add(left(), right()) << std::endl;
21 }
In this example, you call an adder function with two parameters, both are the
results of function calls on the same line. Their evaluation order is not
specified by the standard and as the invoked functions operate on a global
variable, the result depends on the evaluation order.
I recommend using Wandbox if you quickly want to run your code with
different compilers and versions.
By the way, in the recent standards, a lot of similar cases have been
specified. Check out the references for more details.
References:
Wikipedia
GeeksForGeeks
Undefined behaviour in the STL - Sandor Dargo (C++ on Sea 2020)
Question 102: What is implementation-
defined behaviour?
In many ways, implementation-defined behaviour is similar to unspecified
behaviour. First of all, the standard doesn’t impose on how the concerned
item should be implemented. That will depend on the compiler.
It’s also true that the effects of implementation-defined behaviour are limited
to the very commands in question. So no full execution branches can be
removed, like in the case of undefined behaviour.
If you think about SQL and how ORDER BY works on NULL values you’ll find
that it differs between the different implementations. Some will put NULL at
the beginning of an ordered result set, some will put such values in the back.
You don’t have to figure this out by trying, you’ll find it in the documentation,
it’s implementation-defined behaviour.
References:
In fact, the compiler can remove whole execution paths. Let’s say you have a
big function with one line invoking undefined behaviour. It’s possible that the
whole function will be removed from the compiled code.
When you have undefined behaviour in your code, you break the rules of the
language and it won’t be detected until runtime. Turning on as many compiler
warnings as possible usually helps a lot in removing undefined behaviour.
References:
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:
Using try-catch blocks will not going to work, undefined behaviour is not
about exceptions handled in the wrong way. Similarly, some explicit checks
for example on input containers are going to work either.
1 std::copy_if(numbers21.begin(), numbers22.end(),
2 std::back_inserter(copiedNumbers),
3 [](auto number) {return number % 2 == 1;});
There is no valid check to avoid such a typo, but it’s true that with some
defensive programming you could handle the situation.
1 std::vector<int> doCopy(std::vector<int> numbers) {
2 std::vector<int> copiedNumbers;
3 std::copy_if(numbers.begin(), numbers.end(),
4 std::back_inserter(copiedNumbers),
5 [](auto number) {return number % 2 == 1;});
6 return copiedNumbers;
7 }
8
9 // ...
10 auto copiedNumbers = doCopy(numbers);
This is just a simple example, but the bottom line is that by limiting the scope
of a function, by limiting the number of available variables, you can limit the
compilable typos you can make. Yet, it doesn’t look nice to wrap every
algorithm like that.
- You can listen to your compiler! Turn on whatever warnings you can (-
Wall, -Wextra, -Wpedantic) and treat them as errors. They will catch a lot
of undefined behaviour.
Here is an example:
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:
It’s a set of template classes and functions to provide solutions for common
problems. The elements of the STL can be divided into 4 categories:
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.
“A raw loop is any loop inside a function where the function serves a
purpose larger than the algorithm. implemented by the loop.” - Sean Parent,
C++ Seasoning.
If you have to write something a thousand times, there is a fair chance that
you’ll make some mistakes once in a while. It’s OK, we all make mistakes.
On the other hand, if you use functions that were written before and used a
million times, you won’t face any bugs.
At the same time, chances are good that your for loops are not optimized
either. And it’s alright. Of course, as you write your loops, you are in control.
You can optimize them, you can get the last cycles out of them. You cannot do
the same with the already written functions of a library, even if it’s the
standard library.
But what reasonably could be done, was already done for the standard
algorithms, and most of those things are are not performed for the raw loops
we so often used. In the end, algorithms have better performance.
Raw loops contain low-level code. When you call algorithms, those calls
expressive.\nLet’s take a simple example.\nWhat does the following piece of
code do?
1 const std::vector<int> v{1, 2, 3, 4, 5};
2 auto ans = false;
3 for (const auto& e : v) {
4 if (e % 2 == 0) {
5 ans = true;
6 break;
7 }
8 }
After some thinking, we can say that it tells you if there is any even element
in the vector v.\nHow could we make it more readable? With an algorithm:
1 const std::vector<int> v{1, 2, 3, 4, 5};
2
3 const auto ans = std::any_of(v.begin(), v.end(),
4 [](const auto& e) {return e \
5 % 2 ==0;});
How much easier is that?\nAnd this was only one simple example.
Conclusion
Algorithms are most of the time better than plain old for loops.
They are less error-prone than loops as they were already written and tested
- a lot. Unless you are going for the last drops of performance, algorithms
will provide be good enough for you and actually more performant than
simple loops.
But the most important point is that they are more expressive. It’s
straightforward to pick the good among many, but with education and
practice, you’ll be able to easily find an algorithm that can replace a for loop
in most cases.
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.
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.
First, what can be the intention behind such code? With std::transform we
try to combine the content of two vectors, by adding up the nth element of the
values with the nth element of the otherValues and we push the sum to the
results vector.
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.
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.
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.
So on the stack, you’ll have the above-mentioned pointer and some other
extra variables to keep track of the size and the capacity. You can take a look
at this illustration.
Other implementations are also possible when iterators are stored pointing at
the beginning of the allocated memory, at the current end and at the end of the
total capacity. Check this illustration.
The bottom line is that you have some variables in the main vector object that
help to find the data that is actually stored on the heap. That memory on the
heap must be contiguous and if your vector grows and an extension is
required it might happen that the whole content must be copied to somewhere
else.
In order to protect yourself from these extra copies, if you know how big
your vector will grow, it’s good to reserve that capacity right away after its
declaration, by calling std::vector<T>::reserve(maxCapacity)
References:
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.
There are two main arguments. One is that algorithms and containers are
well-separated concerns in the STL. The other one is about the lack of virtual
constructors.
But are these valid concerns?
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 leading to strange application behaviour.
But the lack of virtual destructor doesn’t lead to undefined behaviour 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, you are perfectly fine to use this
technique. Simply, you have to respect the limitations, though probably this is
not the best strategy to use in the case of a public library. But more on that
just in a second.
So the other main concern is that you might mix containers and algorithms in
your new object. And it’s bad because the creators of the STL said so. And
so what? Alexander Stepanov who originally designed the STL and the
others who have been later contributed to it are smart people and there is a
fair chance that they are better programmers than most of us. They designed
functions, objects that are widely used in the C++ community. I think it’s okay
to say that they are used by everyone.
Most probably we are not working under such constraints, we are not
preparing something for the whole C++ community. We are working on
specific applications with very strict constraints. Our code will not be
reused as such. Never. We don’t work on generic libraries, we work on one-
off business applications.
As long as we keep our code clean (whatever it means), it’s perfectly fine to
provide a non-generic solution.
If you put nothing between the curly braces, you’ll get a compilation error as
the compiler is unable to deduce ‘std::initializer_list<auto>’ from ‘<brace-
enclosed initializer=”” list=””>()’</brace-enclosed></auto>. So no empty
container, but a compilation error.
If you have at least one element between the braces, the type will be
std::initializer_list.
If you wonder what this type is, you should know that it is a lightweight
proxy object providing access to an array of objects of type const T. It is
automatically constructed when:
Since C++11 all STL containers have cbegin and cend member functions
producing const_iterators even if the container itself is not const.
Since C++14, you also have cbegin, cend, crbegin, crend non-member
functions available. It’s better to use the non-member functions in case you
want generic code, because these will also work on built-in arrays, not like
the member functions.
It’s all about contracts and principles. In C++, one of the foundational
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 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)
To get more detailed information about the categories, check out this link.
References:
CPlusPlus.com: Iterator
Learn C++: 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.
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.
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.
References:
Here is an example:
1 int calculateArea(int a, int b);
2 int calculateArea(int a, int b=2);
3 int calculateArea(int a=3, int b=2);
These default values are used if one or more arguments are left blank while
calling the function - according to the number of left blank arguments.
Let’s see a complete example. If the value is not passed for any of the
parameters during the function call, then the compiler uses the default
value(s) provided. If a value is specified, then the default value is stepped on
and the passed value is used.
1 #include <iostream>
2
3 int calculateArea(int a=3, int b=2) {
4 return a*b;
5 }
6
7 int main() {
8 std::cout << calculateArea(6, 4) << '\n';
9 std::cout << calculateArea(6) << '\n';
10 std::cout << calculateArea() << '\n';
11 }
12 /*
13 24
14 12
15 6
16 */
This means that that the int calculateArea(int a=5, int b); would
not compile. On the other hand, int g(int n = 0, ...) would compile as
ellipsis does not count as a parameter.
One last thing to note is that you should always declare your defaults in the
header, not in the cpp file.
If not, don’t worry. It’s not evident. b points to a derived class, yet B’s default
value was used.
You might expect 42 twice in a row, but that’s incorrect. The code won’t
compile. The overriding function doesn’t “inherit” the default value, so the
empty fun call to Derived2 fails.
1 main.cpp: In function 'int main()':
2 main.cpp:28:10: error: no matching function for call to '\
3 Derived2::fun()'
4 28 | d2->fun();
5 | ~~~~~~~^~
6 main.cpp:19:8: note: candidate: 'virtual void Derived2::f\
7 un(int)'
8 19 | void fun(int p) override {
9 | ^~~
10 main.cpp:19:8: note: candidate expects 1 argument, 0 pr\
11 ovided
Now let’s modify a bit our original example and we’ll ignore Derived2.
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 }
It is going to be:
1 Derived::fun 42
2 Derived::fun 13
The reason is that a virtual function is called on the dynamic type of the
object, while the default parameter values are based on the static type. The
dynamic type is Derived in both cases, but the static type is different, hence
the different default values are used.
References:
GotW.ca: Herb Sutter
SonarSource
While the usual answer is “yes, of course”, it’s not the correct answer. If you
just think about the standard library itself, many classes don’t have a virtual
destructor. That’s something I wrote about in strongly-types containers. So if
for example std::vector doesn’t have a virtual destructor, the answer “of
course”, cannot be correct.
Any operation that will be performed through the base class interface, and
that should behave virtually, should be virtual. If deletion, therefore, can be
performed polymorphically through the base class interface, then it must
behave virtually and must be virtual. The language requires it - if you delete
polymorphically without a virtual destructor, you have to face something that
is called undefined behaviour. Something that we always want to avoid.
1 class Base { /*...*/ };
2
3 class Derived : public Base { /*...*/ };
4
5 int main() {
6 Base* b = new Derived;
7 delete b; // Base::~Base() had better be virtual!
8 }
But base classes need not always allow polymorphic deletion. In such cases,
make base class destructor protected and non-virtual. It’ll make deletes
through base class pointers fail:
1 class Base {
2 /*...*/
3 protected:
4 ~Base() {}
5 };
6
7 class Derived : public Base { /*...*/ };
8
9 int main() {
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 */
References:
mutable specifier
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.
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 };
If you declare a lambda mutable, then it allows the lambda to modify the
objects captured by copy, and to call their non-const member functions.
Otherwise, it’s not possible.
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.
It means that if you declare a variable volatile, you acknowledge that the
value stored at the variable’s memory address might change independently.
Speaking of this latter one, while it’s possible, according to the C++11 ISO
Standard, the volatile keyword is only meant for use for hardware access; do
not use it for inter-thread communication.
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.
Any change to an inline function could require all clients of the function to be
recompiled because the compiler would need to replace all the code once
again otherwise it will continue with old functionality.
To inline a function, simply place the keyword inline before the function
name and define the function before any calls are made to the function. It’s
important to remember that it’s often only a hint to the compiler. The
compiler can ignore the inline qualifier in case the defined function is more
than a line.
When the program executes the function call instruction the CPU stores the
memory address of the instruction following the function call, copies the
arguments of the function on the stack and finally transfers control to the
specified function. The CPU then executes the function code, stores the
function return value in a predefined memory location/register and returns
control to the calling function.
This can become overhead if the execution time of a function is less than the
switching time from the caller function to called function. For functions that
are large and/or perform complex tasks, the overhead of the function call is
usually insignificant compared to the amount of time the function takes to run.
However, for small, commonly-used functions, the time needed to make the
function call is often a lot more than the time needed to actually execute the
function’s code. This overhead occurs for small functions because the
execution time of a small function is less than the switching time.
What we should take away is that the usage of the inline keyword is only a
request to the compiler, it is NOT a command. The compiler might ignore it
and at the same time, it might inline functions as a form of optimization even
if it was not requested. It can bring some performance advantages in case the
function is small and often used, but at the same time, it can damage the
caching punctuality and bloat binary size.
References:
CPlusPlus.com
Geeks For Geeks
Tutorials Point
Question 124: What do we catch?
Consider the following piece of code. What is going to be the output if we
uncomment the commented print statements in the two catch blocks? And
why so?
1 #include <iostream>
2 #include <exception>
3
4 class SpecialException : public std::exception {
5 public:
6 virtual const char* what() const throw() {
7 return "SpecialException";
8 }
9 };
10
11 void a() {
12 try {
13 throw SpecialException();
14 } catch (std::exception e) {
15 // std::cout << "exception caught in a(): " << e.what\
16 () << '\n';
17 throw;
18 }
19 }
20
21 int main () {
22 try {
23 a();
24 } catch (SpecialException& e) {
25 // std::cout << "exception caught in main(): " << e.w\
26 hat() << '\n';
27 }
28 }
The output is apart from a compiler warning advising you not to catch
anything by value is:
1 exception caught in a(): std::exception\nexception caught\
2 in main(): SpecialException\n
Let’s have a look at the code once again. There is a new exception type
declared (1). In function a() we throw it (2) and then right there we catch a
quite generic std::exception by value (3). After logging it, we rethrow the
exception (4). In main(), we catch our custom exception type by const
reference (5):
1 #include <iostream>
2 #include <exception>
3
4 class SpecialException : public std::exception { // 1
5 public:
6 virtual const char* what() const throw() {
7 return "SpecialException";
8 }
9 };
10
11 void a() {
12 try {
13 throw SpecialException(); // 2
14 } catch (std::exception e) { // 3
15 std::cout << "exception caught in a(): " << e.what() \
16 << '\n';
17 throw; // 4
18 }
19 }
20
21 int main () {
22 try {
23 a();
24 } catch (SpecialException& e) { //5
25 std::cout << "exception caught in main(): " << e.what\
26 () << '\n';
27 }
28 }
When we caught and logged a standard exception by value (3), we lost some
of the information. Even though originally a SpecialException was thrown
(2), in order to squeeze it into an std::exception variable, the compiler
had to get rid of some parts of that exception. In other words, it got sliced.
Had we caught it by reference, we would have kept its original type.
So in the above case, the original exception is rethrown (4), not the one we
use within the catch block, but the one that left the try block. We keep that
narrower SpecialException.
A general rule of thumb is to always catch exceptions by reference to avoid
superfluous copies and to be able to make persistent changes to the caught
exception.
Both pointers and references can also be used to gain performance by saving
the resources needed to copy big objects, e.g. when the objects are passed
into a function or when they are returned.
While the above restrictions prevail, at the same time references are safer
and easier to use:
1. Safer: Since references must be initialized, wild references like wild
pointers are unlikely to exist. It is still possible to have references that don’t
refer to a valid location, they are called dangling references.
1-2) That’s fairly simple. We declare an unsigned int in both cases and we
initialize them with a positive number. They both compile and if you print the
value of a, it’ll be 42 in both cases, there is nothing to see here, move along.
If you imagine a number line where there are no negative numbers, left to
zero will be the largest number so the value of -42 will be the maximum
value of an unsigned integer minus 41. Why 41 and not 42? Because -1 in fact
equals the highest number that we can represent. From there we have to step
41 to the left to get to -42, which will be exactly
std::numeric_limits<unsigned int>::max()-41</unsigned>.
By the way, no matter what platform you use
std::numeric_limits<unsigned int>::max()</unsigned> will give
you the actual highest value for an unsigned int and as you could guess you
can pass in other types as a template parameter.
But why?
In C++, if the types of two operands differ from one another, then the operand
with the “lower” or “smaller” type will be promoted to the type of the
“higher” or “larger” type operand implicitly.
long double
double
float
unsigned long int
long int
unsigned int
int
So when the two operands are, as in our example, 25u (unsigned int) and
50 (int), the 50 is promoted to also being an unsigned int (i.e., 50u).
The result of the operation will be of the type of the operands. Therefore, the
result of 25u - 50u will itself be an unsigned int as well. So the result of
-25 converts to 4294967271 when promoted to being an unsigned int.
In fact, the result is the maximum value of an unsigned integer - (50 - 25 + 1).
The plus one is there, because, between 1 and the maximum value of an
unsigned int, there is zero as well that we have to take into account.
1 #include <iostream>
2 #include <numeric>
3
4 int main() {
5 auto a = std::numeric_limits<unsigned int>::max()+1 - 2\
6 5;
7 auto b = 25u - 50;
8 std::cout << std::boolalpha << (a == b) << '\n';
9 }
The difference between the meaning of pre and post depends upon how the
expression is evaluated and the result is stored.
To view this from another perspective, the prefix operator returns a reference
of the variable (T& T::operator++();), while the postfix a copy (T
T::operator++(int);).
This implies that while in the language name, there is a postfix increment
(C++), in our old raw for loops we got used to writing 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.
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:
1 a = 9 // was not modified
2 b = 10
3 c = 11
In case you guessed that b is 11, let’s get back to the postfix increment. What
happens is that first, c’s value is copied into b (both b and c is 10 at the
moment) and just then c is incremented and becomes 11. But that doesn’t
affect b’s value. If instead, we had the line b = ++c, both would become 11.
If the correct order of precedence is not followed then one possibility is:
1 c = a + 1 + 1 * 0
2 c = 9 + 1 + 1 * 0
3 c = 10 + 1 * 0
4 c = 11 * 0
5 c = 0 // this is wrong
Question 130: Does this string declaration
compile?
Will the following code compile? If not, why not? If it does, why?
Whether this code compiles or not depends on whether you treat warnings as
errors or not. Let’s assume you don’t handle warnings as errors.
This code does exactly one thing, it creates an empty string and assigns it to a
variable called foo.
Why does this matter? Who writes anything like that in real life?
We only have to complexify this example a little bit. Think about a mutex.
1 #include <mutex>
2
3 static std::mutex m_mutex;
4 static int shared_resource;
5
6 void increment_by_42() {
7 std::unique_lock<std::mutex>(m_mutex);
8 shared_resource += 42;
9 }
What is happening here?
At the beginning of this mail, you might have thought about that okay, we
create a temporary unique_lock, locking mutex m_mutex. Well. No. I think
you can tell on your own what’s happening there. It creates a lock on the type
of a mutex and called that lock m_mutex. And nothing got locked.
But if you express your intentions by naming that lock, or if you brace
initialization it’ll work as expected.
1 #include <mutex>
2
3 static std::mutex m_mutex;
4 static int shared_resource;
5
6 void increment_by_42() {
7 std::unique_lock aLock(m_mutex); // this works fine
8 // std::unique_lock<std::mutex> {m}; // even this would\
9 work fine
10 shared_resource += 42;
11 }
By the way, using -Wshadow compiler option would have also caught the
problem by creating a warning. Treat all warnings as errors and be safe!
It lets you initialize class members where they are declared not in the
constructors.
1 class T {
2 public:
3 T()=default;
4 T(int iNum, std::string iText) : num(iNum), text(iText)\
5 {};
6
7 private:
8 int num{0}; // Default member initialization
9 std::string text{}; // Default member initialization
10 };
In the above example, the members num and text are initialized with 0 and
with an empty string right where they are declared. As such, we can keep the
default constructor simpler.
If you consistently follow this practice you will not have to worry that
you forgot to initialize something
And you’ll not have to scroll anywhere else to find the default value of
the variable.
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 guidelines also encourages us to use default
member initialization for initialization data members instead of the default
constructor.
Then, for example, when we try to call empty() on it, and we have the
following error message (with gcc):
1 main.cpp:18:5: error: request for member 'empty' in 'foo'\
2 , which is of non-class type 'std::string()' {aka 'std::_\
3 _cxx11::basic_string<char>()'
What happened is that the above line of code was interpreted as a function
declaration. We just declared a function called foo, taking no parameters and
returning a string. Whereas we only wanted to call the default constructor.
This can give a kind of headache to debug even if you know about the most
vexing parse. Mostly because you see the compiler error on a different line,
not when you declare your <del>varibale</del> function, but when you try to
use it.
This can be fixed very easily. You don’t need to use parentheses at all to
declare a variable calling its default constructor. But since C++11, if you
want you can also use the {}-initialization. Both examples are going to work
just fine:
1 std::string foo;
2 std::string bar{};
Declare the MyInt object outside of the call, on the previous line, but
then it won’t be a temporary anymore.
Replace any or both of the parentheses with the brace initialization.
Both Doubler d{MyInt(i)}; or Doubler d(MyInt{i}) would work,
just like Doubler d{MyInt{i}}. And this third one is inconsistent at
least in how we call the constructors. The potential downside is that this
only works since C++11.
If you are using an older version of C++ than C++11, you can add an
extra pair of parentheses around the argument that is meant to be sent to
the constructor: Doubler d((MyInt(i))). This also makes it
impossible to parse it as a declaration.
This type is needed because it’s quite cheap to copy it as it only needs the
above-mentioned copy and its length. We can effectively use it for read
operations, and besides it received the remove_prefix and remove_suffix
modifiers.
As you might have guessed, the reason why string_view has these two
modifiers, because it’s easy to implement them without modifying the
underlying character sequence. Either the start of the character sequence or
its length should be modified.\n \nIf your function doesn’t need to take
ownership of the string argument and you only need read operations (plus
the above mentioned two modifiers) use a string_view instead. In fact,
string_view should replace all const std::string& parameters.
There is one drawback though. As under the hood you can either have a
std::string or a std::string_view, you lose the implicit null
termination. If you need that, you have to stick with std::string (const&)
The above-mentioned fact that it’s quite cheap to copy, and that it has one
less level of pointer indirection than a std::string& can bring a significant
performance gain. If you’re interested in the details, please read this article.
This has changed with C++20, where starts_with and ends_with were
added both to std::sting and std::string_view and performing such
checks became extremely simple and readable!
1 #include <iostream>
2 #include <string>
3
4 int main() {
5 std::string s{"what a string"};
6 std::cout << std::boolalpha;
7 std::cout << s.starts_with("what") << '\n';
8 std::cout << s.ends_with("not") << '\n';
9 }
10 /*
11 true
12 false
13 */
Before C++20 it was still easy to perform similar tests, though a bit less
readable and more verbose.
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.
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';
12 std::cout << boost::algorithm::ends_with(s, "string") <\
13 < '\n';
14 std::cout << boost::algorithm::ends_with(s, "what") << \
15 '\n';
16 }
References:
Boost Header
C++ Reference: compare
C++ Reference: starts_with
C++ Reference: ends_with
Find out if string ends with another string in C++ - Stackoverflow
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.
References:
- Abseil.io
- Wikipedia: copy elision
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. lvalues could
only appear on the left-hand side of an assignment operator.
prvalue
Some examples:
xvalue
An “eXpiring” value is an expression whose evaluation determines the
identity of an object, bit-field, or function whose resources can be reused.
(Unlike lvalues whose resources cannot be reused.)
Some examples:
Reference:
The reason behind is that x which is an int is cast into an unsigned int
Due to the conversion, x becomes - depending on your platform -
4294967293 (2^32 -3).
You should avoid comparing signed with unsigned integers unless you use
C++20 and you have access to std::cmp_equal and to its friends. For more
details, check out the references.
References:
As an example, if you execute: ./myProg foo bar 'b a z', argc will be
3 and argv will be ["myProg", "foo", "bar", "b a z"].
References:
Though if you are a library maintainer, you might have to take binary
compatibility into consideration. Adding a new parameter to a function, even
if it has a default argument, breaks binary compatibility. On the other hand,
adding a new overload does not break binary compatibility.
References:
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.
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:
A switch statement is more readable, even if you take into account all the
necessary break statements.
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:
In order to understand this better, we have to remind ourselves that before the
compilation the preprocessor replaces all the #include statements with the
textual copy of the included file.
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:
For files that are in the same project, files that exist at the relative path to the
including file, one should use the double-quotes.
For files from the standard library, or in fact, from any library that you
depend on, the angle brackets form should be preferred.
1 // foo.cpp:
2 // From the standard library, requires the <> form
3 #include <string>
4
5 // A file that is not locally relative,
6 // included from another library; use the <> form
7 #include <some_library/common.h>
8
9 // A file locally relative to foo.cpp
10 // in the same project, use the "" form
11 #include "foo.h"
12
13
14 // A file locally relative to foo.cpp
15 // in the same project, use the "" form
16 #include "foo_utils/utils.h"
If you use double quotes, it will first look up the file in the local relative path
first and then if it failed to file a match, it will look for it anywhere else it’s
possible.
This also means that if you use double quotes to include a file from another
library and a file with the same name is created in your local project at the
same relative path, you will have a bad surprise while compiling.
These are the commonly followed best practices, for exact information, it’s
worth checking your compilers implementation.
References:
It’s true that with multiple return statements you’ll have multiple exit points
that you have to consider when you try to understand what a function does.
But this is only a problem if your function is too big. If you write short
functions, let’s say what fits a screen (use a nice, big font size) then this is not
a problem.
Besides, with multiple return statements, it’s easy to create some guards on
top of the function:
1 void foo(MyClass* iMyClass) {
2 if (iMyClass == nullptr) {
3 return;
4 }
5 // do stuff
6 }
References: