An AlarmClock
object needs to know when it’s supposed to sound its alarm, so why not include a Time
object as a member of the AlarmClock
class? Such a software-reuse capability is called composition (or aggregation) and is sometimes referred to as a has-a relationship—a class can have objects of other classes as members.5
You’ve actually been using composition since Chapter 3. In that chapter’s examples, class Account
contained a string
object as a data member.
Previously, we saw how to pass arguments to the constructor of an object we created in main
. Now we show how a class’s constructor can pass arguments to member-object constructors via member initializers.
Data members are constructed in the order in which they’re declared in the class definition (not in the order they’re listed in the constructor’s member-initializer list) and before their enclosing class objects are constructed.
The next program uses classes Date
(Figs. 9.18–9.19) and Employee
(Figs. 9.20–9.21) to demonstrate composition. Class Employee
’s definition (Fig. 9.20) contains private
data members firstName
, lastName
, birthDate
and hireDate
. Members birthDate
and hireDate
are const
objects of class Date
, which contains private
data members month
, day
and year
. The Employee
constructor’s prototype (Fig. 9.20, lines 14–15) specifies that the constructor has four parameters (first
, last
, dateOfBirth
and dateOfHire
). The first two parameters are passed via member initializers to the string
class constructor for the firstName
and lastName
data members. The last two are passed via member initializers to the Date
class constructor for the birthDate
and hireDate
data members.
Employee
Constructor’s Member-Initializer ListThe colon (:
) following the constructor’s header (Fig. 9.21, line 13) begins the member-initializer list. The member initializers specify the Employee
constructor parameters being passed to the constructors of the string
and Date
data members. Parameters first
, last
, dateOfBirth
and dateOfHire
are passed to the constructors for objects firstName
, lastName
, birthDate
and hireDate
, respectively. The order of the member initializers does not matter. They’re executed in the order that the member objects are declared in class Employee
.
For clarity, list the member initializers in the order that the class’s data members are declared.
Date
Class’s Default Copy ConstructorAs you study class Date
(Fig. 9.18), notice that the class does not provide a constructor that receives a parameter of type Date
. So, why can the Employee
constructor’s member-initializer list initialize the birthDate
and hireDate
objects by passing Date
objects to their Date
constructors? As we mentioned in Section 9.10, the compiler provides each class with a default copy constructor that copies each data member of the constructor’s argument object into the corresponding member of the object being initialized. Chapter 10 discusses how you can define customized copy constructors.
Date
and Employee
Figure 9.22 creates two Date
objects (lines 9–10) and passes them as arguments to the constructor of the Employee
object created in line 11. There are actually five constructor calls when an Employee
is constructed:
two calls to the string
class’s constructor (lines 13–14 of Fig. 9.21),
two calls to the Date
class’s default copy constructor (lines 15–16 of Fig. 9.21),
and the call to the Employee
class’s constructor.
Line 13 outputs the Employee
object’s data. When each Date
object is created in lines 9–10, the Date
constructor defined in lines 12–20 of Fig. 9.19 displays a line of output to show that the constructor was called (see the first two lines of the sample output). Note that line 11 of Fig. 9.22 causes two Date
constructor calls that do not appear in this program’s output. When each of the Employee
’s Date
member objects is initialized in the Employee
constructor’s member-initializer list (Fig. 9.21, lines 15–16), the default copy constructor for class Date
is called. Since this constructor is defined implicitly by the compiler, it does not contain any output statements to demonstrate when it’s called.]
Class Date
and class Employee
each include a destructor (lines 30–32 of Fig. 9.19 and lines 31–34 of Fig. 9.21, respectively) that prints a message when an object of its class is destructed. This enables us to confirm in the program output that objects are constructed from the inside out and destructed in the reverse order, from the outside in (i.e., the Date
member objects are destructed after the Employee
object that contains them).
Notice the last four lines in the output of Fig. 9.22. The last two lines are the outputs of the Date
destructor running on Date
objects hire
(Fig. 9.22, line 10) and birth
(line 9), respectively. The outputs confirm that the three objects created in main
are destructed in the reverse of the order in which they were constructed. The Employee
destructor output is five lines from the bottom. The fourth and third lines from the bottom of the output window show the destructors running for the Employee
’s member objects hireDate
(Fig. 9.20, line 22) and birthDate
(line 21). The last two lines of the output correspond to the Date
objects created in lines 10 and 9 of Fig. 9.22.
These outputs confirm that the Employee
object is destructed from the outside in— i.e., the Employee
destructor runs first (output shown five lines from the bottom of the output window), then the member objects are destructed in the reverse order from which they were constructed. Class string
’s destructor does not contain output statements, so we do not see the firstName
and lastName
objects being destructed. Again, Fig. 9.22’s output did not show the constructors running for member objects birthDate
and hireDate
, because these objects were initialized with the default Date
class copy constructors provided by the compiler.
If a member object is not initialized through a member initializer, the member object’s default constructor will be called implicitly. Values, if any, established by the default constructor can be overridden by set functions. However, for complex initialization, this approach may require significant additional work and time.
Initialize member objects explicitly through member initializers. This eliminates the overhead of “doubly initializing” member objects—once when the member object’s default constructor is called and again when set functions are called in the constructor body (or later) to initialize the member object.
A compilation error occurs if a member object is not initialized with a member initializer and the member object’s class does not provide a default constructor (i.e., the member object’s class defines one or more constructors, but none is a default constructor).
If a data member is an object of another class, making that member object public
does not violate the encapsulation and hiding of that member object’s private
members. But, it does violate the encapsulation and hiding of the enclosing class’s implementation, so member objects of class types should still be private
.