Using In-member initialization, using constructors smartly and using class members functions in a safe and proper way to avoid mistakes
Table of Contents
- Use member initializers in the same order as their declaration
- Prefer in-class member initializer over constant initializations OR over default constructor.
- Don’t cast away const, ever!
- Use delegating constructors to represent common actions for all constructors of a class.
1. Use member initializers in the same order as their declaration
Member variables are always initialized in the order they are declared in the class definition.
The order in which you write them in the constructor initialization list is ignored 🥴
Make sure the constructor code doesn’t confusingly specify different orders. For e.g. this case as below —
Would lead to issuese
mail is declared before first_name and last_name in the class definition, hence as per the constructor call, it will be initialized first and will attempt to use the other not-yet-initialized fields which are first_name and last_name .
How to make it right
This code harbors a bug that’s as subtly harmful as it is hard to spot hence
Write member initializers in the same order as their declarationMany compilers (but not all) will issue a warning if you break this rule. Modern compilers Clang, MSVC detect it with the right use of right warning flags.
The reason for this language design decision is to ensure there is a unique order to destroy members; otherwise, the destructor would have to destroy objects in different orders, depending on the constructor that built the object.
- Protects you from an oddity of the language without requiring everyone to know it.
- Might encourage you to rethink your class design so this dependency goes away
2. Prefer in-class member initializer over constant initializations OR over default constructor
You should don’t define a default constructor that only initializes data members; use in-class member initializers instead which works as a good fallback in case you forget to initialize something.
Example — A bad class that misses one initialization in a constructor
Cons of not using in-member class initializers
where the following is an example of a much better class
Using in-member class initializers, Nice!!
Using in-class member initializers lets the compiler generate the function for you. Also, the compiler-generated function can be more efficient 😊
- No overhead of taking care of initializing constants separately in each constructor.
- Performance gain by using standard default constructors.
3. Don’t cast away const, ever!
We shouldn’t cast away from getter functions even when there seems a need.
For e.g. — Stuff is a class that does some calculations overnumber1 and number2 and computes the result. Now getValue() const is a function that fetches the value, and being a getter function is marked const.
number1 and number2 are updated byService1() and Service2() functions respectively.
Now, in case read frequency of getValue() is much more than the number of writes, we should preemptively update the cachedValue which is returned.
Such as —
However, in case the number of writes is much more, we should follow a lazy calculation approach where we set a dirty flag such as below —
getValue function would show error as it’s marked constBut this poses a problem because const function can not modify this **newly introduced class member variable **cachedValid .
- A wrong fix **would be to remove const from **getValue() function
- *Another wrong fix **would be to const_cast over “this”* pointer.
Doing this makes a lie out of const. Any variable is actually declared asconst, modifying it may result in undefined behavior.
- Allows getValue() function to change anything in the instance.
- The header file is now speaking a lie basically.
The right fix would be to declare cachedValid and cachedValue as mutable so that thegetValue() function can only modify the mutable ones.
The correct fix
Benefits of correct fix
- Header file tells the truth
- getValue() function can only change the mutable variables
- Code accessing mutable members is shorter and more readable
- Easier to write, read, and maintain
- Const-correctness may enable optimizations 😊
4. Use delegating constructors to represent common actions for all constructors of a class
The common action gets tedious to write and may accidentally not be common. Hence, wherever possible we should refer to existing constructors.
For e.g. — This Date is a bad class.
A bad series of constructors, duplicate logic
Good!! Using delegating constructors
To avoid repetition and accidental differences.
- C++ Coding Standards: 101 Rules, Guidelines, and Best Practices by Herb Sutter, Andrei Alexandrescu
- https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines Thanks for reading this article! Feel free to leave your comments and let me know what you think. Please feel free to drop any comments to improve this article.
Please check out our other articles and website, Have a great day!