Last Update:
Q&A - C++ Initialization
Last time I showed a couple of questions about initialization. Try them here if you haven’t already. In this article, I’ll show you the answers and add more notes about initialization in C++.
About
I selected the following questions from 25 questions that you can find in my C++ Initialization Story book:
Print version @Amazon
C++ Initialization Story @Leanpub
Moreover, in the book, you can find a few coding exercises to practice skills.
The Quiz
The quiz consists of 10 questions; below, I grouped them into categories and explained them in one go.
C++ History:
Which C++ Standard did add in-class default member initializers?
- C++98
- C++11
- C++14
- C++17
Answer: C++11 - added as N2756
Will this code work in C++11?
struct User { std::string name = "unknown"; unsigned age { 0 }; };
User u { "John", 101 };
- Yes, the code compiles in C++11 mode.
- The code compiles starting with C++14 mode.
- The code doesn’t compile even in C++20.
Answer: the code compiles starting with C++14 mode; initially, in C++11, aggregate classes weren’t supported. But thanks to N3653, this improved now.
Additionally: in C++20, we got support for initializing bitfields.
inline
variables
Do you need to define a static inline
data member in a cpp
file?
- No, the definition happens at the same place where a static inline member is declared.
- Yes, the compiler needs the definition in a cpp file.
- Yes, the compiler needs a definition in all translation units that use this variable.
Answer: 1 - with inline variables, there’s no need to separate declaration and definition.
Can a static inline
variable be non-constant?
- Yes, it’s just a regular variable.
- No, inline variables must be constant.
Answer: 1 - it’s a regular variable, so it can be const or not.
See the example:
struct MyClass {
inline static const int sValue = 777;
};
inline int sGlobal = 100;
int main() { }
Run at Compiler Explorer
NSDMI
What’s the output of the following code:
struct S {
int a { 10 };
int b { 42 };
};
S s { 1 };
std::cout << s.a << ", " << s.b;
1, 0
10, 42
1, 42
Answer: 1 and 42, 1
comes from the initializer S s { 1 };
and then the b
member is initialized by NSDMI.
Consider the following code:
struct C {
C(int x) : a(x) { }
int a { 10 };
int b { 42 };
};
C c(0);
Select the true
statement:
C::a
is initialized twice. The first time, it’s initialized with10
and then the second time with0
in the constructor.C::a
is initialized only once with0
in the constructor.- The code doesn’t compile because the compiler cannot decide how to initialize the
C::a
member.
Answer: 2 - this time, the code looks strange, as we have a user-defined constructor. Default member initialization happens before the body of the constructor unless the data member is explicitly mentioned in the constructor initialization list. The code is equivalent to the following:
struct C {
C(int x) : a(x), b (42) { }
int a;
int b;
};
C c(0);
But anyway, the member will be only initialized once, not twice, and the code does compile.
Auto
Assume you have a std::map<string, int> m;
. Select the single true statement about the following loop:
for (const pair<string, int>& elem : m)
- The loop properly iterates over the map, creating no extra copies.
- The loop will create a copy of each element in the map as the type of
elem
mismatches. - The code won’t compile as a
const pair
cannot bind to a map.
Answer: 2 - the value type for std::map
is std::pair<const Key, T>
and not const std::pair<Key, T>
. For our case, the code performed extra copies due to the conversion between std::pair<const std::string, int>
and const std::pair<std::string, int>&
(i.e., const std::string
to std::string
).
Can you use auto
type deduction for non-static data members?
- Yes, since C++11
- No
- Yes, since C++20
Short answer: no… even in C++23 :)
Longer answer:
You can use auto
for static variables:
class Type {
static inline auto theMeaningOfLife = 42; // int deduced
};
However, you cannot use it as a class non-static member:
class Type {
auto myField { 0 }; // error
auto param { 10.5f }; // error
};
The alternative syntax also fails:
class Type {
auto myField = int { 10 };
};
Unfortunately, auto
is not supported. For example, in GCC, I get:
error: non-static data member declared with placeholder 'auto'
It’s easy for the compiler to deduce the type of a static data member as the initialization happens at the place you declare it. However, it’s impossible for regular data members because the initializer might come from the default member init or the constructor (when you override a default value).
Note: Same happens for CTAD and data members.
Other
What happens when you throw an exception from a constructor?
- The object is considered “created” so it will follow the regular lifetime of an object.
- The object is considered “partially created,” and thus, the compiler won’t call its destructor.
- The compiler calls
std::terminate
as you cannot throw exceptions from constructors.
Answer 2:The compiler calls a destructor only for objects that are fully created. Consider a constructor that checks the id
parameter and throws an exception:
class Product {
explicit Product(const char* name, unsigned id) : name_(name), id_(id) {
std::cout << name << ", id " << id << '\n';
if (id < 100)
throw std::runtime_error{"bad id..."};
}
// ...
};
And then:
int main() {
try {
Product tvset("TV Set", 123);
Product car("Mustang", 9);
}
catch (const std::exception& ex) {
std::cout << "exception: " << ex.what() << '\n';
}
}
When we run the example, we’ll get the output:
TV Set, id 123
Mustang, id 9
TV Set destructor...
exception: bad id...
This time the example creates two objects: TV set
and Mustang
. In the output, we can notice that both objects call their constructors, but there’s only one destructor invocation (for TV set
). Since Mustang
threw an exception in the constructor, the destructor won’t be executed.
Since destructors might be called when the compiler performs stack unwinding, they shouldn’t throw exceptions, which might result in callingstd::terminate()
. Read this C++ Core Guideline suggestion for more information: E.16: Destructors, deallocation, andswap
must never fail.
What happens when you compile this code?
struct Point { int x; int y; };
Point pt {.y = 10, .x = 11 };
std::cout << pt.x << ", " << pt.y;
Select the true
statement:
- The code doesn’t compile. Designators have to be in the same order as the data members in the
Point
class. - The code compiles and prints
11, 10
. - The code compiles and prints
10, 11
.
Answer: 1; the initializers have to be in the same order as in the class. See more in my separate blog pos: Designated Initializers in C++20 - C++ Stories
More in the book:
See more questions and the content in the book:
- Buy directly at Leanpub: C++ Initialization Story @Leanpub This platform also gives you a 45-day refund period!
- Buy at @Amazon Print,
- Buy together with my other books: Buy C++17 in Detail, Lambda and Initialization - 33$ Instead of 64$!
- Support me on Patreon Become a Patron - At the 6$ tier, you’ll get the ebook for free!
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: