C++ Core Guidelines: Declarations and Initialisations
Let’s continue our tour through the rules for expressions and statements in the C++ core guidelines. This post will be about declarations and initializations.
To be honest, most of the rules in this post are quite obvious but they often provide one or the other very interesting insight; therefore, I will mainly write in this post about these special aspects. Here are the rules for today:
- ES.11: Use
auto
to avoid redundant repetition of type names - ES.12: Do not reuse names in nested scopes
- ES.20: Always initialize an object
- ES.21: Don’t introduce a variable (or constant) before you need to use it
- ES.22: Don’t declare a variable until you have a value to initialize it with
- ES.23: Prefer the
{}
-initializer syntax - ES.24: Use a
unique_ptr<T>
to hold pointers
Here are the details.
ES.11: Use auto
to avoid redundant repetition of type names
The example from the guidelines is not promising to me. So, let me give you another one. Changing your code may become a piece of cake if you use auto.
The following example is based on auto. You do have not to think about the types; therefore, you can not make an error. This means the type of res will be int at the end.
auto a = 5; auto b = 10; auto sum = a * b * 3; auto res = sum + 10; std::cout << typeid(res).name(); // i
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
If you decide to change the literal b from int to double (2) or use in (3) a float literal instead of the int literal. No problem. It will be automatically handled for you.
auto a = 5; auto b = 10.5; // (1) auto sum = a * b * 3; auto res = sum * 10; std::cout << typeid(res).name(); // d auto a = 5; auto b = 10; auto sum = a * b * 3.1f; // (2) auto res = sum * 10; std::cout << typeid(res).name(); // f
ES.12: Do not reuse names in nested scopes
This is one of these quite apparent rules. You should not reuse names in nested scopes for readability and maintenance reasons.
// shadow.cpp #include <iostream> int shadow(bool cond){ int d = 0; if (cond){ d = 1; } else { int d = 2; d = 3; } return d; } int main(){ std::cout << std::endl; std::cout << shadow(true) << std::endl; std::cout << shadow(false) << std::endl; std::cout << std::endl; }
What will be the output of the program? Confused by the d’s? Here is the result.
This was easy! Right? But the same phenomenon is quite surprising in class hierarchies.
// shadowClass.cpp #include <iostream> #include <string> struct Base{ void shadow(std::string){ // 2 std::cout << "Base::shadow" << std::endl; } }; struct Derived: Base{ void shadow(int){ // 3 std::cout << "Derived::shadow" << std::endl; } }; int main(){ std::cout << std::endl; Derived derived; derived.shadow(std::string{}); // 1 derived.shadow(int{}); std::cout << std::endl; }
Both structs Base and Derived have a method shadow. The one in the base accepts a std::string (2) and the other one an int (3). When I invoke the object derived with a default-constructed std::string (1), I may assume that the base version will be called. Wrong! Because the method shadow is implemented in the class Derived, the methods of the base class will not be considered during name resolution. Here is the output of my Gcc.
To fix this issue, shadow must be known to Derived.
struct Derived: Base{ using Base::shadow; // 1 void shadow(int){ std::cout << "Derived::shadow" << std::endl; } };
You have to put a Base::shadow (1) into Derived. Now the program behaves as expected.
ES.20: Always initialize an object
The rules on which object will be initialized or not are pretty tricky to get right in C++. Here is a simple example.
struct T1 {}; class T2{ public: T2() {} }; int n; // OK int main(){ int n2; // ERROR std::string s; // OK T1 t1; // OK T2 t2; // OK }
n is a global variable, which will be initialized to 0. This will not hold for n2 because it is a local variable and will not be initialized. But they will be initialized if you use a user-defined type such as std::string, T1, or T2 in a local scope.
If that is too difficult for you, I have a simple fix. Use auto. Now, you can not forget to initialize the variable. The compiler will check this.
struct T1 {}; class T2{ public: T2() {} }; auto n = 0; int main(){ auto n2 = 0; auto s = ""s; auto t1 = T1(); auto t2 = T2(); }
ES.21: Don’t introduce a variable (or constant) before you need to use it
I think this is trivial. We program C++, not C.
ES.22: Don’t declare a variable until you have a value to initialize it with
You may have a so-called used-before-set error if you don’t follow this rule. Have a look at the guidelines.
int var; if (cond) // some non-trivial condition Set(&var); else if (cond2 || !cond3) { var = Set2(3.14); } // use var
Do you know if one of the conditions holds? If not, var as a local built-in variable is not initialized.
ES.23: Prefer the {}
-initializer syntax
There are a lot of reasons for using {}-initialization:
- always applicable
- overcomes the most vexing parse
- prevents narrowing
You have to keep a particular rule in mind. If you combine auto with an {}-initialization, you will get an std::initializer_list in C++14 but not in C++17.
For all the details, read my previous post to {}-Initialisation.
ES.24: Use a unique_ptr<T>
to hold pointers
I will make it short. A std::unique_ptr<T> is as efficient as a raw pointer by design but has a great added value: it takes care of its resource. This means: don’t use a raw pointer. If you are curious about the details of std::unique_ptr, read my two posts to std::unqiue_ptr.
What’s next?
We are not done with the rules for declarations in C++. The remaining will follow in the next post.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!