Full Ebook of C Initialization Story A Guide Through All Initialization Options and Related C Areas Bartlomiej Filipek Online PDF All Chapter
Full Ebook of C Initialization Story A Guide Through All Initialization Options and Related C Areas Bartlomiej Filipek Online PDF All Chapter
https://ebookmeta.com/product/options-futures-and-other-
derivatives-eleventh-edition-global-john-c-hull/
https://ebookmeta.com/product/options-the-essential-guide-for-
getting-started-in-derivatives-trading-tenth-edition-michael-c-
thomsett/
https://ebookmeta.com/product/data-structures-through-c-4th-ed-
yashavant-kanetkar/
https://ebookmeta.com/product/head-first-c-a-learners-guide-to-
real-world-programming-with-c-and-net-core-4th-edition-stellman/
All In Roulette 2 1st Edition C B Alice
https://ebookmeta.com/product/all-in-roulette-2-1st-edition-c-b-
alice/
https://ebookmeta.com/product/options-and-derivatives-
programming-in-c23-algorithms-and-programming-techniques-for-the-
financial-industry-3rd-edition-carlos-oliveira/
https://ebookmeta.com/product/data-structures-and-algorithms-
implementation-through-c-1st-edition-dr-brijesh-bakariya/
https://ebookmeta.com/product/data-structures-through-c-3rd-
ed-1st-edition-yashavant-kanetkar/
https://ebookmeta.com/product/head-first-c-a-learner-s-guide-to-
real-world-programming-with-c-and-net-core-4th-edition-andrew-
stellman/
C++ Initialization Story
A Guide Through All Initialization Options and Related
C++ Areas
Bartłomiej Filipek
This book is for sale at http://leanpub.com/cppinitbook
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.
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii
Revision History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
5. Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Objects allocated on the heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Destructors and data members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Virtual destructors and polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Partially created objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
A compiler-generated destructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Summary and use cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
About the Book
Initialization in C++ is a hot topic! The internet is full of discussions about best practices,
and there are even funny memes on that subject. The situation is not surprising, as there are
more than a dozen ways to initialize a simple integer value, complex rules for the auto-type
deduction, data members, and object lifetime nuances.
And here comes the book.
Throughout this text, you will learn practical options to initialize various categories of
variables and data members in Modern C++. More specifically, this text teaches multiple
types of initialization, constructors, non-static data member initialization, inline variables,
designated initializers, and more. Additionally, you’ll see the changes and new techniques
from C++11 to C++20 and lots of examples to round out your understanding.
The plan is to explain most (if not all) parts of initialization, learn lots of excellent C++
techniques, and see what happens under the hood.
Learning objectives
The goal is to equip you with the following knowledge:
• Explain rules about object initialization, including regular variables, data members,
and non-local objects.
i
About the Book ii
Prerequisites
• You should have basic knowledge of C++ expressions and primitive types.
• You should be able to implement an elementary class with several data members. Know
how to create and manipulate objects of such a class in a basic way.
Example code
You can find source code of all examples in this separate GitHub public repository.
the link will appear later
You can browse individual files or download the whole branch:
the link will appear later
Code license
The code for the book is available under the Creative Commons License.
Formatting
Code samples are presented in a monospaced font, similar to the following example:
For longer examples:
Title Of the Example
#include <iostream>
int main() {
const std::string text { "Hello World" };
std::cout << text << '\n';
}
int foo() {
return std::clamp(100, 1000, 1001);
}
When available, you’ll also see a link to online compilers where you can play with the code.
For example:
About the Book v
#include <iostream>
int main() {
std::cout << "Hello World!";
}
You can click on the link in the title, and then it should open the website of a given online
compiler (in the above case, it’s Compiler Explorer). You can compile the sample, see the
output, and experiment with the code directly in your browser. Here’s a basic overview of
Compiler Explorer:
Snippets of longer programs were usually shortened to present only the core mechanics.
The current version of the book might show some limitations regarding syntax highlighting.
For example:
• Template method is not highlighted C++ lexer doesn’t recognize function if return type
is templated · Issue #1138⁴.
• Modern C++ attributes are sometimes not appropriately recognized.
Special sections
Throughout the book, you can also see the following sections:
This is an Information Box with extra notes related to the current section.
This is a Warning Box with potential risks and threats related to a given topic.
This is a Quote Box. In the book, it’s often used to quote the C++ Standard.
⁴https://github.com/pygments/pygments/issues/1138
⁵https://github.com/pygments/pygments/issues?q=is%3Aissue+is%3Aopen+C%2B%2B
About the Author
Bartłomiej (Bartek) Filipek is a C++ software developer from a beautiful city Cracow in
Southern Poland. He started his professional career in 2007 and in 2010 he graduated from
Jagiellonian University with a Masters Degree in Computer Science.
Bartek currently works at Xara⁶, where he develops features for advanced document editors.
He also has experience with desktop graphics applications, game development, large-scale
systems for aviation, writing graphics drivers and even biofeedback. In the past, Bartek
has also taught programming (mostly game and graphics programming courses) at local
universities in Cracow.
Since 2011 Bartek has been regularly blogging at cppstories.com⁷ (started as bfilipek.com⁸).
The blog focuses on core C++ and getting up-to-date with the C++ Standards. He’s also a
co-organiser of the C++ User Group in Cracow⁹. You can hear Bartek in one @CppCast
episode¹⁰ where he talks about C++17, blogging and text processing.
Since October 2018, Bartek has been a C++ Expert for the Polish National Body which works
directly with ISO/IEC JTC 1/SC 22 (C++ Standardisation Committee). Bartek was awarded
his first MVP title for the years 2019/2020 by Microsoft.
In his spare time, he loves collecting and assembling Lego models with his son.
Bartek is the author of C++17 In Detail¹¹ and C++ Lambda Story¹²
⁶http://www.xara.com/
⁷https://www.cppstories.com]
⁸https://www.bfilipek.com
⁹https://www.meetup.com/C-User-Group-Cracow/
¹⁰http://cppcast.com/2018/04/bartlomiej-filipek/
¹¹https://leanpub.com/cpp17indetail
¹²https://leanpub.com/cpplambda
vii
Acknowledgements
This book wouldn’t be possible without valuable input from many C++ experts and friends.
I especially would like to thank to the following people:
They spent a lot of time on finding even little things that could be improved and extended.
Last but not least, I got a lot of feedback and comments from the blog readers, Patreon
Discord Server (See @C++Stories @Patreon²³), and discussions at C++ Polska²⁴. Thank you
all!
With all of the help from those kind people, the book quality got better and better!
¹³https://www.linkedin.com/in/florin-ioan-chertes-41b6845/
¹⁴https://pl.linkedin.com/in/konrad-ja%C5%9Bkowiec-84585159
¹⁵https://home.agh.edu.pl/~cyganek/
¹⁶https://blog.panicsoftware.com/
¹⁷https://javierestrada.blog/
¹⁸https://www.fluentcpp.com/
¹⁹https://andreasfertig.blog/
²⁰https://sommerlad.ch/
²¹https://timur.audio/
²²https://twitter.com/timur_audio
²³https://www.patreon.com/cppstories
²⁴https://cpp-polska.pl/
viii
Revision History
• 20th June 2022 - The first public version! The books is almost done. Missing parts:
some sections in 10. Containers as Data Members, some sections in 11. Non-regular
Data Members.
• 22nd June 2022 - new sections on NSDMI, direct init and parens, more about inheriting
constructors, link to GoodReads, wording, hotfixes.
• 24th June 2022 - updated the “copy and move constructor” chapter, typos and small
wording improvements.
• 16th July 2022 - Containers as Data Members chapter rewritten, noexcept consistency
and noexcept move operations advantages in the move constructor section, wording,
fixes, layout.
• 13th September 2022 - changed title to “C++ Initialization Story”, adapted book
structure, rewritten “Non-local objects” chapter (previously only on inline variables),
new extracted chapter on Techniques, new section on CRTP.
• 18th November 2022 - heavily updated and completed “Non-regular data members”
chapter, constinit and thread_local sections in the “Non-local objects” chapter,
filled the “implicit conversion” section in the Constructors chapter.
• 23rd December 2022 - content completed! Added Deduction chapter, filled missing
sections in the Techniques chapter. Layout improvements, a few more questions,
exercises and fixes.
ix
1. Local Variables and Simple Types
Let’s start simple and ask, “what is initialization?” When we go to the definition from
C++Reference¹, we can read:
void foo() {
int x = 42;
// ... use 'x' later...
}
Above, we have a function with a local variable x. The variable is declared as integer and
initialized with the value 42. This is one of many ways you can assign that initial value. Here
are some more options:
1
Local Variables and Simple Types 2
You can also come up with many other forms of setting a value. We can also extend the
syntax on class data members, static variables, thread locals, or even dynamic memory
allocations.
In theory, initialization is simple: “put a value into a memory location of a newly created
variable”. However, such action relates to many parts of application code (local vs. non-local
scope vs. thread) and various places in the memory (like stack vs. heap). That’s why the
syntax or the behavior might be slightly different.
In C++, we have at least the following forms of initialization:
• aggregate initialization
• constant initialization
• default initialization
• direct initialization
• copy initialization
• list initialization
• reference initialization
• value initialization
• zero initialization
• plus related topics like copy elision, static variables, conversion sequences, constructors,
assignment, dynamic memory, storage, and more.
While the list sounds complex, we’ll move through those topics step by step revealing core
concepts. Later we’ll address more advanced examples and see what happens inside the C++
machinery.
While we can explain most cases on integers and other numerical types, it’s best to work
on something more practical. The book starts with some elementary custom types, then
considers various issues we might have with their early implementations. Later the types
will expand, giving us more context and compelling use cases.
Interface). C++ provides a set of built-in types, including boolean, integral, character, and
floating-point. Additionally, you can use objects from the Standard Library, like various
collections, std::string, std::vector, std::map, std::set, and many others. You can
collect these essential components and build your types.
To create a background for our main topic, let’s start with a type representing Car
Information for a car listing app. A system reads the car/truck information from a database
and displays it in the application. For an easy start, the type holds four members: name (a
std::string), production year, number of seats, and engine power.
Below there’s the first version of the code for that CarInfo type:
Ex 1.1. Simple CarInfo structure. Run @Compiler Explorer
#include <iostream>
#include <string>
struct CarInfo {
std::string name;
unsigned year;
unsigned seats;
double power;
};
int main() {
CarInfo firstCar;
firstCar.name = "Renault Megane";
firstCar.year = 2003;
firstCar.seats = 5;
firstCar.power = 116;
Local Variables and Simple Types 4
In the above example, we defined a simple structure that holds data for a CarInfo. The code
is super simple, contains some issues, and follows the style of C++03. In the following few
chapters, I’ll guide you through the code and help you understand the problems and how to
eliminate them. We’ll also modernize it to include the latest C++ (up to C++20) features.
First: name, year, seats, and power are called non-static data members. Each instance of
the CarInfo class has its own set of those members. In other words, we group variables to
create a representation for models in our problem domain. A user-defined type might also
have static data members, which are data shared between all instances of a given type. For
example, we could imagine a static member variable called numAllCars that would indicate
the total number of cars created in our program. We’ll talk about static data members later
in chapter 11 Static Variables.
Now, let’s investigate the code in detail. The definition and the declaration of the variable
firstCar in the main() function:
CarInfo firstCar;
It is called default initialization and, since our struct is simple, will leave all data members
of built-in types with indeterminate values. Similarly, you can get the same (potentially
buggy effect) for simple types when declared in function (as such variables have automatic
storage duration) ²:
void foo() {
int i; // indeterminate value!
double d; // indeterminate value!
}
The std::string data member name, on the other hand, will have an empty state (an empty
string) because its default constructor will be called. More on that later.
²In contrast, static and thread-local objects will be zero-initialized.
Local Variables and Simple Types 5
Once the object is created and uninitialized, we can access its members and set proper values.
By default, struct has public access to its members (and class has private access). This way,
we can access and change their values directly.
The output:
name:
year: 0
seats: 0
power (hp): 0
The initialization with empty braces {} is called value initialization and, by default (for built-
in types and classes with default constructors that are neither user-provided nor deleted),
sets data to “zero” (adapted for different types). This is similar to declaring and defining the
following variables:
³chapterinlinevars
Local Variables and Simple Types 6
int i{}; // i == 0
double d{}; // d == 0.0
std::string s{}; // s is an empty string
This time the storage duration doesn’t matter, and value initialization works the same for
static, dynamic, thread-local, or automatic variables. For types with default constructors
(more on that later), the code will call them and, in the case of string s; will initialize it
to an empty string.
// arrays:
int arr[] { 1, 2, 3, 4 };
float numbers[] = { 0.1f, 1.1f, 2.2f, 3.f, 4.f, 5. };
int nums[10] { 1 }; // 1, and then all 0s
// structures:
struct Point { int x; int y; };
struct Line { Point p1; Point p2; };
Line longLone {0, 0, 100, 100};
Line anotherLine = {100}; // rest set to 0
Line shortLine {{-10, -10}, {10, 10}}; // nested
• You can use list initialization for arrays, and when the number of elements is not
provided, the compiler will deduce the count.
• If you pass fewer elements in the initializer list than the number of elements in the
array, the remaining elements will be value initialized. For built-in types, it means the
value of zero.
• For structures, you can use a single initializer list or nested one; the expansion will be
recursive.
• If you provide fewer values than the number of data members in the aggregate, then the
remaining data members (in the declaration order) will be effectively value initialized.
The first bullet point says that each element is copy initialized. We’ll return to this topic and
explain the difference between a copy vs. direct initialization syntax once we know explicit
constructors.
For our structure, we can write the following test code:
Ex 1.4. Aggregate initialization for the CarInfo structure. Run @Compiler Explorer
struct CarInfo {
std::string name;
unsigned year;
unsigned seats;
double power;
};
int main() {
CarInfo firstCar{"Megane", 2003, 5, 116 };
printInfo(firstCar);
CarInfo partial{"unknown"};
printInfo(partial);
CarInfo largeCar{"large car", 1975, 10};
printInfo(largeCar);
}
Local Variables and Simple Types 8
Without going into full definitions, an aggregate means a simple type (or an array) with all
public data members, no virtual functions, and user-provided constructors.
We’ll discuss aggregates in further parts of the book (and see the full definition of that
category of types). And there’s a dedicated chapter about Aggregates and Designated
Initialization in C++20.
#include <iostream>
#include <string>
struct CarInfo {
std::string name { "unknown" };
unsigned year { 1920 };
unsigned seats { 4 };
double power { 100. };
};
int main() {
CarInfo unknown;
printInfo(unknown);
CarInfo zeroed{};
Local Variables and Simple Types 9
printInfo(zeroed);
CarInfo partial{"large car", 1975};
printInfo(partial);
}
The syntax is quite intuitive; you can initialize a data member at the place where it’s declared.
This can prevent accidental bugs where your data has some indeterminate value. As you
can see from the example, even if you use default initialization or value initialization, data
members will get values that were provided in the struct declaration. If you give fewer
values in the aggregate initializer, the remaining members will get their defaults from the
declaration.
Technically, in-class member initializers have been available since C++11, but aggregate
types weren’t supported initially. In this section, we’ve only scratched the surface of
this handy technique. See the dedicated chapter for this topic: Non-static data member
initialization chapter.
Summary
In this chapter, we covered some simple custom types and looked at ways to initialize their
data members. We went from objects with indeterminate values to zero initialization, and
then we learned about aggregates and techniques to provide default values.
Things to keep in mind:
• Default initialization for objects and variables yields indeterminate values for built-
in types or default-initialize complex types (like std::string and set it to an empty
string). That’s why it’s essential to be sure your objects and simple variables are
always initialized.
• Value initialization like int x{}; for built-in types effectively yields zero initialization
for them so that they will be zero (in their type).
Local Variables and Simple Types 10
• With value initialization CarInfo car{}; all data members will be zero-initialized
(for built-in types) or default initialized for complex types.
• Aggregates are simple types or arrays with all public data members; we can initialize
them with an aggregate initialization syntax.
• Thanks to the in-class member initializer feature, you can provide default values for
your data members.
What’s next?
While simple types are handy, in C++, we often need to build large objects where data
members depend on each other or have invariants. In such cases, it’s best to hide them
behind member functions and give access to them under certain conditions. In the next
chapter, we’ll look at class’s and constructors. We’ll also expand the knowledge that we
got so far.
2. Initialization With Constructors
In the previous chapter, you’ve seen that C++ might treat simple structures with all public
data members as an aggregate class. Still, aggregates are insufficient if we want better data
encapsulation and a more complex class API. For full flexibility in C++, we can leverage
constructors that are special member functions invoked when an object is created.
¹https://en.wikipedia.org/wiki/Checksum
11
Initialization With Constructors 12
The class above contains three non-static data members: data_, checkSum_, and serverID_.
I’m using the underscore suffix to indicate private data members, a common practice in
many codebases. See Google C++ Style Guide².
To keep things simple, I implemented the calcCheckSum function in terms of
std::accumulate(), which is an algorithm from the C++ Standard Library. This
code starts from 0 and adds numerical values of letters from the input std::string. Since
C++23, we can use the 0UZ integer literal to represent the size_t so that it matches with
the return type for the function; alternatively, we could use static_cast<size_t> or
UL/ULL for 32/64-bit systems respectively. For example, for "HELLO", we’ll get the following
computations:
DataPacket has so-called getters and setters - functions that return or change a particular
data member. For example getData() returns the data_ data member, while setData(...)
allows us to change it.
One important topic is that getters usually have const applied at the end. This means that
a given member function is constant and cannot change the value of the members (unless
they are mutable). If you have a const object, you can only call its const member functions.
Applying const might improve program design as it’s usually easier to reason about the state
of const instances. For more information, see this C++ core guideline: Con.2: By default,
make member functions const³.
Member functions might also have noexcept specifier applied. However, this
topic is outside the scope of the book and won’t be covered. You can find more
@C++Reference - noexcept specifier⁴.
²https://google.github.io/styleguide/cppguide.html#Variable_Names
³https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#con2-by-default-make-member-functions-const
⁴https://en.cppreference.com/w/cpp/language/noexcept_spec
Initialization With Constructors 13
Here’s the continuation of the example where we create and use the object of the DataPacket
class:
Ex 2.2. Simple DataPacket class, continuation. Run @Compiler Explorer
int main() {
DataPacket packet;
packet.setData("Programming World");
std::cout << packet.getCheckSum() << '\n';
}
The code doesn’t access data members directly but calls member functions to operate on the
object and change its properties.
You can notice the public and private parts in the class declaration. The order of those
sections is just a coding convention, and they group elements together based on their access
modifier. In short, a member under the public keyword can be accessed from the outside
(like calling a member function or accessing a data member). On the other hand, members
under the private section cannot be accessed from outside⁵. In C++, you can also add
protected to your class declaration, which means that member functions or fields are
not accessible outside, but they are accessible to all inherited classes (assuming public
inheritance, see more on that in the inheritance section further in the book).
For example, in the main() function above, I cannot write:
DataPacket packet;
packet.serverId = 10; // error: 'size_t DataPacket::serverId' is private...
The only difference between class and struct in C++ is that class has private
as the default access modifier and private inheritance, while struct has both
specified as public. Some C++ guidelines, for example, Google Style Guide see
this link⁶, suggest using struct only for smaller, “passive” types, with only public
data members. The C++ Core Guidelines also recommend using class if any
member is not public; see C++ Core Guidelines - C.8⁷.
Since our class doesn’t have any user-defined constructors (more on them in the next section),
we can also use value initialization syntax to set values to zero or default values:
⁵Unless accessed by friend functions or classes.
⁶https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes
⁷https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c8-use-class-rather-than-struct-if-any-member-is-non-
public
Initialization With Constructors 14
Ex 2.3. Value initialization for the DataPacket class. Run @Compiler Explorer
int main() {
DataPacket packet{};
std::cout << "data: " << packet.getData() << '\n';
std::cout << "checkSum: " << packet.getCheckSum() << '\n';
std::cout << "serverId: " << packet.getServerId() << '\n';
}
data:
checkSum: 0
serverId: 0
However, the main difference now is that because we moved the data members to the private
section, the class is not an aggregate. That’s why we cannot use aggregate initialization to
set all values at once. To fix this, we need to look at constructors. And that is the plan for
further sections.
Basics of constructors
A constructor is a special member function that does not have a name, but we declare/define
it using the enclosing class name⁸. You cannot invoke a constructor like other member
functions. Instead, the compiler calls it when an object of its class is being initialized. It
has the following basic syntax:
• constructor has no name, but we define it using the name of the class,
• optional explicit - keyword to block implicit conversions on a given class type,
• ClassName - the name of the given class type (they have to match),
• parameter-list- a list of parameters, as in a regular function, might be empty
• optional = default/=delete specifies if a constructor should be deleted (not present)
or defaulted by the compiler,
• : - indicates the start of the member/base initialization list, required when
base-class-initializer or member-init lists are present,
• optional base-class-initializer - a list of base classes’ constructors that we
explicitly want to call,
• optional member-init - a list of data members where we can directly initialize them,
• {/*body*/} - a function body.
For illustrative purposes, you can find a simple class type with two data members below.
The Product class will serve as a toy example, and then we’ll apply the knowledge to the
DataPacket class I plan to update. Let’s have a look at one snippet:
class Product {
public:
Product() : id_{-1}, name_{"none"} { } // a default constructor
explicit Product(int id, const std::string& name)
: id_{id}, name_{name} { }
private:
int id_;
std::string name_;
};
The above example shows a class with two constructors. The first is called a default
constructor; it has no arguments. The second one takes two arguments. As you can notice,
⁹https://en.cppreference.com/w/cpp/language/constructor
Initialization With Constructors 16
C++ allows multiple constructors that look like overloaded functions (they differ by the
number or types of arguments). Each constructor also has a regular function body where you
can execute some code; in our case, they are both empty for now. I also applied the explicit
keyword on the second constructor; we’ll talk about it later in the explicit constructors
section.
The primary function of constructors is to perform some actions at the start of a lifetime of
an object. Usually, it means data member initialization, resource allocation (opening a file, a
socket, memory allocation), or even doing some special logic (like logging).
In our case, constructors touch only data members inside a special section of constructors
called member initializer list: like, id_{-1}, name_{"none"}. Inside this initializer list, we
can also call constructors of base classes (if any). Later, we’ll address inheritance in the
Inheritance section.
The member initializer list is more efficient than using the body of a constructor. Sometimes
it’s even the only option to initialize the value, as with types that are not assignable. See the
following and wrong alternative:
class Product {
public:
Product() { id_ = 0; name_ = "none"; } // bad code, only for illustration
private:
int id_;
std::string name_;
};
The code will yield the same values for data members as in the previous example, but the data
members are set in two steps rather than one. With the member initializer list, data members
are set directly, same as calling: int id_ { 0 } or std::string name_ {"none"}. On the
other hand, if we use assignment in the constructor body, it requires two steps:
While this might not be a big issue for built-in simple types like int , you’ll need some
more CPU cycles for larger objects like strings. Please don’t write such code and aim for a
member initializer list to initialize your data members efficiently.
There’s also one important aspect about the initializer list: the order of initialization. This is
covered in The C++ Specification: 11.10.3 Classes¹⁰:
Non-static data members are initialized in the order they were declared in the class
definition (regardless of the order of the mem-initializers).
The values will be set correctly, but the order will differ from what we think. Here’s the
warning from GCC compiled with -Wall option (experiment @Compiler Explorer¹¹):
In constructor 'Product::Product()':
warning: 'Product::name_' will be initialized after [-Wreorder] ...
warning: 'int Product::id_' [-Wreorder] ...
The initialization order might be critical when you imply some dependency on the values.
For example, we can write the following artificial sample:
struct S {
int x;
int y;
int z;
S(): x{0}, y{1}, z{x+y} { }
// S(): y{0}, z{0}, x{z+y}, { }
};
In the above example, the first constructor initializes x and y and then uses those values to
initialize z. This is complicated and might be hard to read, but it works correctly. On the
¹⁰https://timsong-cpp.github.io/cppwp/n4868/class.base.init#13.3
¹¹https://godbolt.org/z/jE77169qd
Initialization With Constructors 18
other hand, in the second (commented out) constructor, the order of initialization will create
an undefined behavior for initializing x, as z and y won’t be initialized yet. It’s best to avoid
such dependencies to minimize the risk of bugs.
Let’s see how a constructor works by creating some objects of the Product class:
Product none;
In the first example, we created the none object, which is default constructed. The compiler
will call our default constructor; thus, the data members will be initialized to id_ = -1 and
name_ = "none".
The example uses the form of direct initialization, which calls the constructor with two
arguments. After the call, data members will be: id_ = 10 and name_ = "car".
And the last example:
This time we also called a constructor with two arguments, but the syntax is called * direct
list initialization* - "{}". Please notice that I also used this form of initialization inside the
initializer list in constructors.
Here’s the complete example:
Ex 2.4. Constructors for the Product class. Run @Compiler Explorer
#include <iostream>
#include <string>
class Product {
public:
Product() : id_{-1}, name_{"none"} { } // a default constructor
explicit Product(int id, const std::string& name)
: id_{id}, name_{name} { }
private:
int id_;
std::string name_;
};
int main() {
Product none;
std::cout << none.Id() << ", " << none.Name() << '\n';
You might also scratch your head and ask why I declared the name parameter as const
std::string& rather than just std::string&. First, we don’t want to modify this parameter
in the constructor’s body. What’s more, const T& - const references can bind to “temporary”
objects like a string literal "super car". Without a const reference, we would have to pass
some named string object. Alternatively, we can pass the name by value and perform a
“move operation” on that argument. Further in the book, I’ll address this topic in detail; see
chapter: A Use Case - Best Way to Initialize string Data Members.
The syntax with curly braces “{}” is, in fact, a powerful feature of C++11 called list
initialization, also called “uniform” or “brace” initialization. The primary motivation was
to create a uniform way to initialize data and avoid some issues.
For example, because of the C++ language grammar rules, the following line won’t compile:
Initialization With Constructors 20
#include <iostream>
#include <string>
struct Box { };
struct Product {
Product(): name{"default product"} { }
Product(const Box& b) : name{"box"}{ }
std::string name;
};
int main() {
Product p(); // << 1.
std::cout << p.name;
Product p2(Box()); // << 2.
std::cout << p2.name;
}
The line Product p(); looks innocent, and one could expect a default constructor to be
called. Unfortunately, the compiler recognizes it as a declaration of a function! There’s a C++
rule which says that anything that can be parsed as a declaration must be interpreted as one.
In our context, the line might mean a local function of a name p returning Product and
taking no arguments. Similarly, the line with p2 also causes compiler errors, and this time
the compiler thinks we declare a local function p2 returning a Product and taking Box as
an argument.
But fortunately, we have at least two ways of fixing it:
Product p{};
Product p1;
Product p2{Box()};
Product p3{Box{}};
It works as expected, and the list initialization syntax is the most consistent option. Try to
modify the example @Compiler Explorer¹² and fix the code. Here’s a good article if you want
¹²https://godbolt.org/z/c48K7c9vq
Initialization With Constructors 21
to know more about this rule: The Most Vexing Parse: How to Spot It and Fix It Quickly -
Fluent C++¹³.
List initialization also handles multiple arguments and can be used inside initializer lists:
Ex 2.6. Multiple arguments and braces. Run @Compiler Explorer
#include <iostream>
#include <string>
struct Product {
Product() : name{"default product"}, value{} { }
Product(char a, char b, char c, double v)
: name{a, b, c}, value{v} { }
std::string name;
double value;
};
int main() {
Product def{};
std::cout << def.name << ", " << def.value << '\n';
Product p{'x', 'y', 'z', 100.0};
std::cout << p.name << ", " << p.value;
}
In the above example, we not only used list initialization to call the Product constructor
with four arguments, but we also used it to initialize the name and value data members.
What’s more, the curly list initialization has the following advantages:
There are some annoyances with list initialization through. For example:
¹³https://www.fluentcpp.com/2018/01/30/most-vexing-parse/
Initialization With Constructors 22
Above you can see a very similar declaration of vectors, but when used with list initialization,
you end up with a different vector than when using direct initialization. The list version
calls a special constructor taking std::initializer_list<int>, while the second calls a
constructor taking (size_type count, const int& value = int()).
Additionally, for auto type deduction in C++14:
Fortunately, this “inconsistency” was fixed in C++17, and now auto k{42} deduces an int.
See more C++17 in details: fixes and deprecation @C++ Stories¹⁴.
C++ Core Guidelines suggest sticking to this way of initialization, as its benefits outweigh
the opposing sides. See ES.23¹⁵
Exception: For containers, there is a tradition for using {…} for a list of elements and (…)
for sizes: vector<int> v(10); // 10 elements with the default value 0
vector<int> v2{10}; // vector of 1 element with the value 10
In this book, I’ll use {} for variable initialization and mention exceptions if needed.
¹⁴https://www.cppstories.com/2017/05/cpp17-details-fixes-deprecation/#new-auto-rules-for-direct-list-initialization
¹⁵https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es23-prefer-the--initializer-syntax
Initialization With Constructors 23
Body of a constructor
After the member initializer list, each constructor has a regular function body, { ... },
where you can perform additional steps to modify variables or call other functions. The only
difference between a regular function and a constructor is that a constructor cannot return
any values. Typically, a constructor throws an exception to report an error.
Here’s a small example that shows how to add some logging into a constructor body and
throw an exception on error:
Ex 2.7. Logging in a constructor. Run @Compiler Explorer
#include <iostream>
#include <stdexcept> // for std::invalid_argument
class Product {
public:
explicit Product(int id, const std::string& name)
: id_{id}, name_{name} {
std::cout << "Product(): " << id_ << ", " << name_ << '\n';
if (id_ < LOWEST_ID_VALUE)
throw std::invalid_argument{"id lower than LOWEST_ID_VALUE!"};
}
std::string Name() const { return name_; }
private:
int id_;
std::string name_;
};
int main() {
try {
Product car(10, "car");
std::cout << car.Name() << " created\n";
Product box(-101, "box");
std::cout << box.Name() << " created\n";
}
catch (const std::exception& ex) {
std::cout << "Error - " << ex.what() << '\n';
Initialization With Constructors 24
}
}
The above example shows a constructor that performs logging and basic parameter checking.
It uses a LOWEST_ID_VALUE, a global constant marked with the constexpr keyword (the
second time we used this keyword).
The constexpr specifier has been available since C++11 and guarantees that a
value is available at compile time for constant expressions. For example, you
can use such a variable to set the number of elements in a C-style array. It’s
often perceived as a “type-safe macro definition”. The keyword applies to all
built-in trivial types like integral values, floating-point, or even character literals
(in C++20, std::string might be used in the constexpr context but not for
variables available at runtime); there’s also a way to declare custom constexpr-
ready types. You can also create a function to be constexpr and possibly evaluate
it at compile-time; however, we won’t cover such functions in this book. See more
at C++Reference - constexpr¹⁶.
If you run this program, you can see the following output:
Please notice that while two constructors were called, we can see that only the first one
succeeded. Since the constructor for box threw an exception, this object is not treated as
fully created. More on that later when we’ll talk about destructors.
¹⁶https://en.cppreference.com/w/cpp/language/constexpr
Initialization With Constructors 25
class DataPacket {
std::string data_;
size_t checkSum_;
size_t serverId_;
public:
DataPacket() : data_{}, checkSum_{0}, serverId_{0} { }
explicit DataPacket(const std::string& data, size_t serverId)
: data_{data}, checkSum_{calcCheckSum(data)}, serverId_{serverId}
{ }
int main() {
DataPacket empty;
printInfo(empty);
DataPacket zeroed{};
printInfo(zeroed);
DataPacket packet{"Hello World", 101};
printInfo(packet);
DataPacket reply{"Hi, how are you?", 404};
printInfo(reply);
}
The output:
Initialization With Constructors 26
data:
checkSum: 0
serverId: 0
data:
checkSum: 0
serverId: 0
data: Hello World
checkSum: 1052
serverId: 101
data: Hi, how are you?
checkSum: 1375
serverId: 404
• The first is a default constructor and initializes data members to default values. It will
be called for default and value initialization.
• The second constructor takes several arguments and matches them with data members.
This constructor makes it easy to pass parameters all at once (previously, we needed
to call setters). This one takes two parameters, but we can initialize as many data
members as we need. For example, the constructors ensure the checkSum_ variable
matches data_. Since those two members are related, thanks to constructors and the
setData member function, we keep the relation safe.
We can also use default member initializers inside a class, but we’ll address that in detail in
a separate chapter.
class Example {
public:
std::string Name() const { return name_; }
private:
std::string name_;
};
A simple rule is that if a class has no user-declared constructors, the compiler will create a
default one if possible.
Have a look:
Ex 2.10. Implicit default constructor. Run @Compiler Explorer
struct Value {
int x;
};
struct CtorValue {
CtorValue(int v): x{v} { }
int x;
};
int main() {
Value v; // fine, default constructor available
// CtorValue y; // error! no default ctor available
CtorValue z{10}; // using custom ctor
}
As you can see above, the compiler will create an implicit default constructor for the Value
class (since it has no other constructors), but it won’t generate a default constructor for the
CtorValue class. Also, notice that Value::x will have an indeterminate value as a default
constructor is empty and won’t set any value for x.
You can control the creation of such a default constructor using two keywords, default
and delete. In short, default tells the compiler to use the default implementation, while
delete blocks the implementation.
struct Value {
Value() = default;
int x;
};
struct CtorValue {
CtorValue() = default;
CtorValue(int v): x{v} { }
int x;
};
struct DeletedValue {
DeletedValue() = delete;
DeletedValue(int v): x{v} { }
int x;
};
int main() {
Value v; // fine, default constructor available
CtorValue y; // ok now, default ctor available
CtorValue z{10}; // using custom ctor
// DeletedValue w; // err, deleted ctor!
DeletedValue u{10}; // using custom ctor
}
In the above example, you can see that we declare Value() = default; this tells the
compiler to create an empty (doing nothing) implementation. Also, in the CtorValue class,
we also use the same technique, and, as you can notice, the default construction works now.
The third class has = delete as its default constructor, and you’ll get an error if you want
to create an object of this class using its default constructor.
The implicit default constructor won’t be created if your type has data members that are
not default-constructible or inherits from a type that is not default-constructible. That
Initialization With Constructors 29
includes references, const data members, unions, and others. See the complete list here
@C++Reference¹⁷.
You may also ask what’s the difference between Value() = default and
Value() { }; they are both “empty”. Still, according to the C++ Standard, the
second constructor is considered user-declared or user-provided and has some
consequences in the type characteristics. We’ll cover that later once we cover copy
constructors in the section: Trivial classes and user-declared/user-provided default
constructors.
struct Product {
Product() : name{"default product"}, value{} { }
Product(int v) : name{"basic"}, value{v} { }
Product(const std::string& n, int v)
: name{n}, value{v} { }
std::string name;
int value;
};
The code looks fine, but now you can create Product objects in a bit unusual way:
We can read that those two lines create products, but what values do the data members
get? It needs to be clarified! The case in the first line is especially interesting, as I passed a
double value of 100.2, and the compiler tried to convert it into the int type (a narrowing
conversion) and then passed it to the constructor.
What’s more, it’s even more problematic with implicit conversions for function calls:
Ex 2.12. Implicit Conversions. Run @Compiler Explorer
int main() {
double someRandomNumber = 100.1;
printProduct(someRandomNumber);
printProduct({"a box", 2});
}
The output:
basic, 100
a box, 2
The key idea is to understand: when you pass arguments into a function call, then the
compiler performs copy initialization on the arguments.
As you can see, the main issue is with constructors that take only one argument (or have
other arguments set to some default value). But even with several arguments, the conversion
can happen when you pass an initialization list.
To prevent such unwanted and unexpected conversions, it’s good to apply the explicit
keyword.
When we apply it:
*****