Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Modern C++ Programming Cookbook

You're reading from   Modern C++ Programming Cookbook Master Modern C++ with comprehensive solutions for C++23 and all previous standards

Arrow left icon
Product type Paperback
Published in Feb 2024
Publisher Packt
ISBN-13 9781835080542
Length 816 pages
Edition 3rd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Marius Bancila Marius Bancila
Author Profile Icon Marius Bancila
Marius Bancila
Arrow right icon
View More author details
Toc

Table of Contents (15) Chapters Close

Preface 1. Learning Modern Core Language Features 2. Working with Numbers and Strings FREE CHAPTER 3. Exploring Functions 4. Preprocessing and Compilation 5. Standard Library Containers, Algorithms, and Iterators 6. General-Purpose Utilities 7. Working with Files and Streams 8. Leveraging Threading and Concurrency 9. Robustness and Performance 10. Implementing Patterns and Idioms 11. Exploring Testing Frameworks 12. C++ 20 Core Features 13. Other Books You May Enjoy
14. Index

Understanding the various forms of non-static member initialization

Constructors are places where non-static class member initialization is done. Many developers prefer assignments in the constructor body. Aside from the several exceptional cases when that is actually necessary, initialization of non-static members should be done in the constructor’s initializer list or, as of C++11, using default member initialization when they are declared in the class. Prior to C++11, constants and non-constant non-static data members of a class had to be initialized in the constructor. Initialization on declaration in a class was only possible for static constants. As we will see here, this limitation was removed in C++11, which allows the initialization of non-statics in the class declaration. This initialization is called default member initialization and is explained in the following sections.

This recipe will explore the ways non-static member initialization should be done. Using the appropriate initialization method for each member leads not only to more efficient code but also to better organized and more readable code.

How to do it...

To initialize non-static members of a class, you should:

  • Use default member initialization for constants, both static and non-static (see [1] and [2] in the following code).
  • Use default member initialization to provide default values for members of classes with multiple constructors that would use a common initializer for those members (see [3] and [4] in the following code).
  • Use the constructor initializer list to initialize members that don’t have default values but depend on constructor parameters (see [5] and [6] in the following code).
  • Use assignment in constructors when the other options are not possible (examples include initializing data members with the pointer this, checking constructor parameter values, and throwing exceptions prior to initializing members with those values or self-references to two non-static data members).

The following example shows these forms of initialization:

struct Control
{
  const int DefaultHeight = 14;                                // [1]
  const int DefaultWidth  = 80;                                // [2]
  std::string text;
  TextVerticalAlignment valign = TextVerticalAlignment::Middle;   // [3]
  TextHorizontalAlignment halign = TextHorizontalAlignment::Left; // [4]
  Control(std::string const & t) : text(t)      // [5]
  {}
  Control(std::string const & t,
    TextVerticalAlignment const va,
    TextHorizontalAlignment const ha):
    text(t), valign(va), halign(ha)             // [6]
  {}
};

How it works...

Non-static data members are supposed to be initialized in the constructor’s initializer list, as shown in the following example:

struct Point
{
  double x, y;
  Point(double const x = 0.0, double const y = 0.0) : x(x), y(y)  {}
};

Many developers, however, do not use the initializer list and prefer assignments in the constructor’s body, or even mix assignments and the initializer list. That could be for several reasons—for large classes with many members, the constructor assignments may be easier to read than long initializer lists, perhaps split into many lines, or it could be because these developers are familiar with other programming languages that don’t have an initializer list.

It is important to note that the order in which non-static data members are initialized is the order in which they were declared in the class definition, not the order of their initialization in a constructor initializer list. Conversely, the order in which non-static data members are destroyed is the reversed order of construction.

Using assignments in the constructor is not efficient, as this can create temporary objects that are later discarded. If not initialized in the initializer list, non-static members are initialized via their default constructor and then, when assigned a value in the constructor’s body, the assignment operator is invoked. This can lead to inefficient work if the default constructor allocates a resource (such as memory or a file) and that has to be deallocated and reallocated in the assignment operator. This is exemplified in the following snippet:

struct foo
{
  foo()
  { std::cout << "default constructor\n"; }
  foo(std::string const & text)
  { std::cout << "constructor '" << text << "\n"; }
  foo(foo const & other)
  { std::cout << "copy constructor\n"; }
  foo(foo&& other)
  { std::cout << "move constructor\n"; };
  foo& operator=(foo const & other)
  { std::cout << "assignment\n"; return *this; }
  foo& operator=(foo&& other)
  { std::cout << "move assignment\n"; return *this;}
  ~foo()
  { std::cout << "destructor\n"; }
};
struct bar
{
  foo f;
  bar(foo const & value)
  {
    f = value;
  }
};
foo f;
bar b(f);

The preceding code produces the following output, showing how the data member f is first default initialized and then assigned a new value:

default constructor
default constructor
assignment
destructor
destructor

If you want to track which object was created and destroyed, you can slightly change the foo class above and print the value of the this pointer for each of the special member functions. You can do this as a follow-up exercise.

Changing the initialization from the assignment in the constructor body to the initializer list replaces the calls to the default constructor, plus the assignment operator, with a call to the copy constructor:

bar(foo const & value) : f(value) { }

Adding the preceding line of code produces the following output:

default constructor
copy constructor
destructor
destructor

For those reasons, at least for types other than the built-in types (such as bool, char, int, float, double, or pointers), you should prefer the constructor initializer list. However, to be consistent with your initialization style, you should always prefer the constructor initializer list when possible. There are several situations when using the initializer list is not possible; these include the following cases (but the list could be expanded for other cases):

  • If a member has to be initialized with a pointer or reference to the object that contains it, using the this pointer in the initialization list may trigger a warning with some compilers that it should be used before the object is constructed.
  • If you have two data members that must contain references to each other.
  • If you want to test an input parameter and throw an exception before initializing a non-static data member with the value of the parameter.

Starting with C++11, non-static data members can be initialized when declared in the class. This is called default member initialization because it is supposed to represent initialization with default values. Default member initialization is intended for constants and members that are not initialized based on constructor parameters (in other words, members whose value does not depend on the way the object is constructed):

enum class TextFlow { LeftToRight, RightToLeft };
struct Control
{
  const int DefaultHeight = 20;
  const int DefaultWidth = 100;
  TextFlow textFlow = TextFlow::LeftToRight;
  std::string text;
  Control(std::string const & t) : text(t)
  {}
};

In the preceding example, DefaultHeight and DefaultWidth are both constants; therefore, the values do not depend on the way the object is constructed, so they are initialized when declared. The textFlow object is a non-constant, non-static data member whose value also does not depend on the way the object is initialized (it could be changed via another member function); therefore, it is also initialized using default member initialization when it is declared. text, conversely, is also a non-constant non-static data member, but its initial value depends on the way the object is constructed.

Therefore, it is initialized in the constructor’s initializer list using a value passed as an argument to the constructor.

If a data member is initialized both with the default member initialization and constructor initializer list, the latter takes precedence and the default value is discarded. To exemplify this, let’s again consider the foo class mentioned earlier and the following bar class, which uses it:

struct bar
{
  foo f{"default value"};
  bar() : f{"constructor initializer"}
  {
  }
};
bar b;

In this case, the output differs as follows:

constructor 'constructor initializer'
destructor

The reason for the different behavior is that the value from the default initializer list is discarded, and the object is not initialized twice.

See also

  • Understanding uniform initialization, to see how brace-initialization works
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image