7.5.1. Constructor Initializer List

Image

When we define variables, we typically initialize them immediately rather than defining them and then assigning to them:

string foo = "Hello World!"; // define and initialize
string bar;                  // default initialized to the empty string
bar = "Hello World!";        // assign a new value to bar

Exactly the same distinction between initialization and assignment applies to the data members of objects. If we do not explicitly initialize a member in the constructor initializer list, that member is default initialized before the constructor body starts executing. For example:

// legal but sloppier way to write the Sales_data constructor: no constructor initializers
Sales_data::Sales_data(const string &s,
                       unsigned cnt, double price)
{
    bookNo = s;
    units_sold = cnt;
    revenue = cnt * price;
}

This version and our original definition on page 264 have the same effect: When the constructor finishes, the data members will hold the same values. The difference is that the original version initializes its data members, whereas this version assigns values to the data members. How significant this distinction is depends on the type of the data member.

Constructor Initializers Are Sometimes Required

We can often, but not always, ignore the distinction between whether a member is initialized or assigned. Members that are const or references must be initialized. Similarly, members that are of a class type that does not define a default constructor also must be initialized. For example:

class ConstRef {
public:
    ConstRef(int ii);
private:
    int i;
    const int ci;
    int &ri;
};

Like any other const object or reference, the members ci and ri must be initialized. As a result, omitting a constructor initializer for these members is an error:

// error: ci and ri must be initialized
ConstRef::ConstRef(int ii)
{              // assignments:
     i = ii;   // ok
     ci = ii;  // error: cannot assign to a const
     ri = i;   // error: ri was never initialized
}

By the time the body of the constructor begins executing, initialization is complete. Our only chance to initialize const or reference data members is in the constructor initializer. The correct way to write this constructor is

// ok: explicitly initialize reference and const members
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) {  }


Image Note

We must use the constructor initializer list to provide values for members that are const, reference, or of a class type that does not have a default constructor.


Order of Member Initialization

Not surprisingly, each member may be named only once in the constructor initializer. After all, what might it mean to give a member two initial values?

What may be more surprising is that the constructor initializer list specifies only the values used to initialize the members, not the order in which those initializations are performed.

Members are initialized in the order in which they appear in the class definition: The first member is initialized first, then the next, and so on. The order in which initializers appear in the constructor initializer list does not change the order of initialization.

The order of initialization often doesn’t matter. However, if one member is initialized in terms of another, then the order in which members are initialized is crucially important.

As an example, consider the following class:

class X {
    int i;
    int j;
public:
    // undefined:  i is initialized before  j
    X(int val): j(val), i(j) { }
};

In this case, the constructor initializer makes it appear as if j is initialized with val and then j is used to initialize i. However, i is initialized first. The effect of this initializer is to initialize i with the undefined value of j!

Some compilers are kind enough to generate a warning if the data members are listed in the constructor initializer in a different order from the order in which the members are declared.


Image Best Practices

It is a good idea to write constructor initializers in the same order as the members are declared. Moreover, when possible, avoid using members to initialize other members.


If possible, it is a good idea write member initializers to use the constructor’s parameters rather than another data member from the same object. That way we don’t even have to think about the order of member initialization. For example, it would be better to write the constructor for X as

X(int val): i(val), j(val) { }

In this version, the order in which i and j are initialized doesn’t matter.

Default Arguments and Constructors

The actions of the Sales_data default constructor are similar to those of the constructor that takes a single string argument. The only difference is that the constructor that takes a string argument uses that argument to initialize bookNo. The default constructor (implicitly) uses the string default constructor to initialize bookNo. We can rewrite these constructors as a single constructor with a default argument (§ 6.5.1, p. 236):

class Sales_data {
public:
    // defines the default constructor as well as one that takes a string argument
    Sales_data(std::string s = ""): bookNo(s) { }
    // remaining constructors unchanged
    Sales_data(std::string s, unsigned cnt, double rev):
          bookNo(s), units_sold(cnt), revenue(rev*cnt) { }
    Sales_data(std::istream &is) { read(is, *this); }
    // remaining members as before
};

This version of our class provides the same interface as our original on page 264. Both versions create the same object when given no arguments or when given a single string argument. Because we can call this constructor with no arguments, this constructor defines a default constructor for our class.


Image Note

A constructor that supplies default arguments for all its parameters also defines the default constructor.


It is worth noting that we probably should not use default arguments with the Sales_data constructor that takes three arguments. If a user supplies a nonzero count for the number of books sold, we want to ensure that the user also supplies the price at which those books were sold.


Exercises Section 7.5.1

Exercise 7.36: The following initializer is in error. Identify and fix the problem.

struct X {
    X (int i, int j): base(i), rem(base % j) { }
    int rem, base;
};

Exercise 7.37: Using the version of Sales_data from this section, determine which constructor is used to initialize each of the following variables and list the values of the data members in each object:

Sales_data first_item(cin);

int main() {
    Sales_data next;
    Sales_data last("9-999-99999-9");
}

Exercise 7.38: We might want to supply cin as a default argument to the constructor that takes an istream&. Write the constructor declaration that uses cin as a default argument.

Exercise 7.39: Would it be legal for both the constructor that takes a string and the one that takes an istream& to have default arguments? If not, why not?

Exercise 7.40: Choose one of the following abstractions (or an abstraction of your own choosing). Determine what data are needed in the class. Provide an appropriate set of constructors. Explain your decisions.

(a) Book

(b) Date

(c) Employee

(d) Vehicle

(e) Object

(f) Tree


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

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