Chapter 22. Initialization Lists

FAQ 22.01 What are constructor initialization lists?

Constructor initialization lists are the best way to initialize member objects.

All member objects are initialized before the body of the constructor begins executing. Constructor initialization lists allow the class to exercise control over the construction of the member objects before the execution of the constructor.

Here is an example of using an initialization list.

image

FAQ 22.02 What will happen if constructor initialization lists are not used?

image

Initialization lists are usually a performance issue, although there are cases when initialization lists impact correctness (see FAQ 22.03 and 22.04). In some cases the code of a constructor can be three times slower if initialization lists are not used. For example, consider the following Person class.

image

The following implementation of the constructor initializes member object name_ using an initialization list. From a performance perspective, it is important to note that the result of the + operator is constructed directly inside member object name_. A temporary object is not needed in this case, and most compilers do not produce an extra temporary object. This typically requires one allocation of memory from the heap and one copy of the data from each string.

image

In contrast, the following constructor sets up member object name_ using assignment. In this case the default constructor (see FAQ 20.08) may have allocated a small amount of memory (many string classes store a '' byte even in cases when the string is empty); then that memory is immediately discarded in the assignment operator. In addition, the compiler will probably have to create a temporary object, and this temporary object is passed into the name_ object's assignment operator; then the temporary is destructed at the ;. That's inefficient. All together, this constructor might make three calls to the memory allocation routines (two allocations, one deallocation) and might copy the string's data twice (once into the temporary and once into name_).

image

Conclusion: All other things being equal, code will run faster with initialization lists than with assignment.

FAQ 22.03 What's the guideline for using initialization lists in constructor definitions?

As a general rule, all member objects and base classes should explicitly appear in the initialization list of a constructor. In addition to being more efficient than default initialization followed by assignment, using the initialization list makes the code clearer since it takes advantage of something that the compiler is going to do anyway.

Note that there is no performance gain in using initialization lists with member objects of built-in types, but there is no loss either, so initialization lists should be used for symmetry.

For an exception to this guideline, see FAQ 22.11.

FAQ 22.04 Is it normal for constructors to have nothing inside their body?

Yes, this happens frequently.

The body of a constructor is the {...} part. A constructor should initialize its member objects in the initialization list, often leaving little or nothing to do inside the constructor's body. When the constructor body is empty, it can be left empty, perhaps ({ }), or decorated with a comment such as

// Intentionally left blank.

An example follows (Fract is a fraction class).

image

The output of this program follows.

a = 0/1
b = 5/1
c = 22/7

Notice that the initialization list resides in the constructor's definition and not its declaration (in this case, the declaration and the definition are separate).

FAQ 22.05 How is a const data member initialized?

Nonstatic const data members are declared in the class body with a const prefix, and their state must be initialized in the constructor's initialization list. The value used to initialize the const data member can be a literal value, a parameter passed to the constructor, or the result of some expression. After initialization, the state of a const data member within a particular object cannot change, but each object can initialize its const data member to a different value.

In the following example, i_ is a non-const member variable and j_ is a const member variable.

image

FAQ 22.06 How is a reference data member initialized?

It must be initialized in the initialization list of each constructor. An example follows.

image

Be sure to avoid binding a reference data member to an object passed to the constructor by value (for example, if parameter i were passed by value), since the reference (i_) would refer to a temporary variable allocated on the stack. This would create a dangling reference since value parameters disappear as soon as the function (the constructor in this case) returns.

Depending on the phase of the moon, a dangling reference might crash the program.

FAQ 22.07 Are initializers executed in the same order in which they appear in the initialization list?

image

Not necessarily.

C++ constructs objects by initializing the subobjects of immediate base classes in the order the base classes appear in the class declaration, then initializing member objects in the order they appear in the class body layout. It uses this ordering so that it can guarantee that base class subobjects and member objects are destructed in the opposite order from which they are constructed. Member objects are destructed in the reverse order of the class body layout, then subobjects of immediate base classes are destructed in the reverse order they appear in the base class list in the class declaration. The order of the initialization list is irrelevant.

The following example demonstrates the fact that initialization order is tied to the order of the class layout rather than to the order of the initialization list. First, class Noisy prints a message during its constructor and destructor.

image

Now class Fred inherits from Noisy and also has two member objects of class Noisy.

image

The constructor of class Fred lists its three Noisy objects in a different order than the one in which they are actually initialized. The important thing to notice is that the compiler ignores the order in which members show up in the initialization list:

image

The constructor's initialization list order is (b_, a_, base-class), but the class body layout order is the opposite: (base-class, a_, b_). The output of this program demonstrates that the initialization list's order is ignored:

construct base
construct a_
construct b_
destruct b_
destruct a_
destruct base

Even though the order of initializers in a constructor's initialization list is irrelevant, see the next FAQ for a recommendation.

FAQ 22.08 How should initializers be ordered in a constructor's initialization list?

Immediate base classes (left to right), then member objects (top to bottom).

In other words, the order of the initialization list should mimic the order in which initializations take place. This guideline discourages a particularly subtle class of order dependency errors by giving an obvious, visual clue. For example, the following contains a hideous error.

image

The output of this program follows.

Y used
Y ctor

Note that y_ is used (Y::f()) before it is initialized (Y::Y()). If the guideline espoused by this FAQ was employed, the error would be more obvious: the initialization list of Z::Z() would have read x_(y_), y_(), visually indicating that y_ was being used before being initialized.

Not all compilers issue diagnostic messages for these cases.

FAQ 22.09 Is it moral for one member object to be initialized using another member object in the constructor's initialization list?

Yes, but exercise great caution.

In a constructor's initialization list, it is best to avoid using one member object from the this object in the initialization expression of a subsequent initializer for the this object. This guideline prevents subtle order dependency errors if someone reorganizes the layout of member objects within the class (see the previous FAQ).

Because of this guideline, the constructor that follows uses s.len_ + 1 rather than len_ + 1, even though they are otherwise equivalent. This avoids an unnecessary order dependency.

image

An unnecessary order dependency on the class layout of len_ and data_ would have been introduced if the constructor's initialization of data_ had used len_+1 rather than s.len_+1. However using len_ within a constructor body ({...}) is okay. No order dependency is introduced since the entire initialization list is guaranteed to finish before the constructor body begins executing.

FAQ 22.10 What if one member object has to be initialized using another member object?

Comment the declaration of the affected data members with the comment //ORDER DEPENDENCY.

If a constructor initializes a member object of the this object using another member object of the this object, rearranging the data members in the class body could break the constructor (see FAQ 22.08). This important maintenance constraint should be documented in the class body. For example, in the constructor that follows, the initializer for data_ uses len_ to avoid a redundant call to strlen(s), thus introducing an order dependency in the class body.

image

Note that the //ORDER DEPENDENCY comment is attached to the affected data members in the class body, not to the constructor initialization list. This is because the order of member objects in the class body is critical; the order of initializers in the constructor initialization list is irrelevant (see FAQ 22.07).

FAQ 22.11 Are there exceptions to the rule “Initialize all member objects in an initialization list”?

image

Yes, to facilitate argument screening.

Arguments to constructors sometimes need to be checked (or screened) before they can be used to initialize a member object. When it becomes difficult to squeeze the resultant if (...) throw ... logic into the initialization list, it may be more convenient to initialize the member object via its default constructor, then modify its state in the constructor body ({...}) via assignment or some other mutative member function.

This situation is usually limited to classes that are built directly on built-in types (int, char*, and so forth), because constructors for user-defined (class) types normally check their own arguments.

For example, in the preceding FAQ, MyString::MyString(const char*) passed its parameter to strlen(const char*) without verifying that the pointer was non-NULL. This test can be implemented by using assignment in the constructor.

image

Using assignment rather than initialization tends to remove order dependencies. For example, MyString::MyString(const char*) no longer introduces an order dependency in the member data of class MyString. However, doing this may introduce performance penalties if the member objects are user-defined (class) types.

FAQ 22.12 How can an array of objects be initialized with specific initializers?

Why use arrays in the first place? Why not use containers, particularly from the standard library? If arrays are a must, and if the elements require specific initializers, the answer is the {...} initializer syntax.

image

The (annotated) output of this program follows.

image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset