The lifecycle of each object starts with its creation; therefore, this post will be about the thirteen most fundamental rules for objects: constructor rules.
Twelve rules are too many for one post. Therefore, I will cover only the first eleven. Why not just ten rules? Because the eleventh rule is just too attractive. The remaining two are part of the next post. Here are the thirteen rules.
- C.40: Define a constructor if a class has an invariant
- C.41: A constructor should create a fully initialized object
- C.42: If a constructor cannot construct a valid object, throw an exception
- C.43: Ensure that a value type class has a default constructor
- C.44: Prefer default constructors to be simple and non-throwing
- C.45: Don’t define a default constructor that only initializes data members; use member initializers instead
- C.46: By default, declare single-argument constructors
- C.47: Define and initialize member variables in the order of member declaration
- C.48: Prefer in-class initializers to member initializers in constructors for constant initializers
- C.49: Prefer initialization to assignment in constructors
- C.50: Use a factory function if you need “virtual behavior” during initialization
- C.51: Use delegating constructors to represent common actions for all constructors of a class
- C.52: Use inheriting constructors to import constructors into a derived class that does not need further explicit initialization
So, let’s look at the rules in detail. For further analysis, use the links to the rules.
An invariant of an object is a characteristic of the object that should hold for its entire lifetime. The place to establish such an invariant is the constructor. An invariant can be a valid date.
This rule is quite similar to the previous one. Accordingly, creating the fully initialized object is the job of the constructor. A class having an init method is asking for trouble.
The user might mistakenly invoke read before init or might forget to invoke init.
Accordingly to the previous rule: throw an exception if you can not construct a valid object. There is not much to add. If you work with an invalid object, you must check its state before its usage. This is highly error-prone. Here is an example from the guidelines:
A value type is a type that behaves like an int. A value type is similar to a regular type. I wrote about value types and regular types in the post about concrete types. Having a default constructor makes it easier to use your type. Many constructors of STL containers rely on the fact that your type has a default constructor. For example, for the value of an ordered associative container such as std::map. If all the class members have a default constructor, the compiler will implicitly generate one for your class.
Error handling is a lot easier with default constructors that can not throw. The guidelines provide a simple example:
C.45: Don’t define a default constructor that only initializes data members; use member initializers instead
This is one of my favorite features of C++11. Initializing class members directly in the class body makes the writing of constructors a lot easier and sometimes obsolete. Class X1 classically defines its members (before C++11) and X2 in a preferred way. A nice side effect is that the compiler automatically generates the constructor for X2.
This is a crucial rule. Single-argument constructors are often called conversion constructors. If you make them not explicit, an implicit conversion may happen.
The implicit conversion from int to String is impossible because the constructor is explicit. If instead of the explicit constructor, the out-commented implicit constructor were used, you would get a string of size 10
The class members are initialized in the order of their declaration. If you initialize them in the constructor initializer in a different order, you may get surprised.
In-class initializer makes it a lot easier to define the constructors. Additionally, you can not forget to initialize a member.
While the in-class initialization establishes the default behavior of an object, the constructor (1) allows the variation of the default behavior.
That is quite an old rule. The most obvious pros of initialization to the assignment are that you can not forget to assign a value and use it uninitialized. Initialization may be faster but never slower than assignment.
Calling a virtual function from a constructor will not work as expected. For protection reasons, the virtual call mechanism is disabled in the constructor because the creation of the derived class hasn’t happened.
Hence, the following example will call the Base version of the virtual function f.
Here is the output of the program.
Let’s create a factory function to have virtual behavior during object initialization. To deal with the ownership, the factory function should return a smart pointer such as a std::unique_ptr or a std::shared_ptr. As a starting point, I will use the previous example but make the constructor of Base protected; therefore, only objects of the class Derived can be created.
At the end of the initialization, the virtual function f (1) should be called. (2) is the factory function. This factory function calls f after creating a std::unique_ptr and returns it. If Derived is derived from Base, then std::unique_ptr<Dervived> is implicitly convertible to a std::unique_ptr<Base>. Finally, we get our virtual behavior during initialization.
There is one risk with this technique. If the base goes out of scope, you must ensure that the Derived destructor is called. This is the reason for the virtual destructor of Base (4). If the destructor is not virtual, you will get undefined behavior. Strange, but if I used a std::shared_ptr instead of a std::unique_ptr for the factory method, the virtual destructor of Base is unnecessary.
Sorry, the post is a little bit too long. But I found, in particular, the last rule (C.50) very interesting; therefore, I had to explain more than usual. In the next post, I will finish the rules for constructors and start with the copy and move rules.
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, Kris Kafka, 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, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, 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, moon, and Philipp Lenk.
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
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
Online Seminars (German)
- C++20: Get the Details (2. Apr 2024 bis 4. Apr 2024)
- Clean Code: Best Practices für modernes C++ (21. Mai 2024 bis 23. Mai 2024)
Programmierung mit modernem C++ (2. Jul 2024 bis 4.
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,