Time Class Case Study: Constructors with Default Arguments

Time Class Case Study Constructors with Default Arguments

The program of Figs. 9.89.10 enhances class Time to demonstrate how arguments are implicitly passed to a constructor. The constructor defined in Fig. 9.2 initialized hour, minute and second to 0 (i.e., midnight in universal time). Like other functions, constructors can specify default arguments. Line 13 of Fig. 9.8 declares the Time constructor to include default arguments, specifying a default value of zero for each argument passed to the constructor. In Fig. 9.9, lines 1417 define the new version of the Time constructor that receives values for parameters HR, min and sec that will be used to initialize private data members hour, minute and second, respectively. Note that class Time provides set and get functions for each data member. The Time constructor now calls setTime, which calls the setHour, setMinute and setSecond functions to validate and assign values to the data members. The default arguments to the constructor ensure that, even if no values are provided in a constructor call, the constructor still initializes the data members to maintain the Time object in a consistent state. A constructor that defaults all its arguments is also a default constructori.e., a constructor that can be invoked with no arguments. There can be a maximum of one default constructor per class.



Figure 9.8. Time class containing a constructor with default arguments.

(This item is displayed on page 494 in the print version)

1 // Fig. 9.8: Time.h 2 // Declaration of class Time. 3 // Member functions defined in Time.cpp. 4 5 // prevent multiple inclusions of header file 6 #ifndef TIME_H 7 #define TIME_H 8 9 // Time abstract data type definition 10 class Time 11 { 12 public: 13 Time( int = 0, int = 0, int = 0 ); // default constructor 14 15 // set functions 16 void setTime( int, int, int ); // set hour, minute, second 17 void setHour( int ); // set hour (after validation) 18 void setMinute( int ); // set minute (after validation) 19 void setSecond( int ); // set second (after validation) 20 21 // get functions 22 int getHour(); // return hour 23 int getMinute(); // return minute 24 int getSecond(); // return second 25 26 void printUniversal(); // output time in universal-time format 27 void printStandard(); // output time in standard-time format 28 private: 29 int hour; // 0 - 23 (24-hour clock format) 30 int minute; // 0 - 59 31 int second; // 0 - 59 32 }; // end class Time 33 34 #endif

Figure 9.9. Time class member-function definitions including a constructor that takes arguments.

(This item is displayed on pages 495 - 496 in the print version)

1 // Fig. 9.9: Time.cpp 2 // Member-function definitions for class Time. 3 #include 4 using std::cout; 5 6 #include 7 using std::setfill; 8 using std::setw; 9 10 #include "Time.h" // include definition of class Time from Time.h 11 12 // Time constructor initializes each data member to zero; 13 // ensures that Time objects start in a consistent state 14 Time::Time( int hr, int min, int sec ) 15 { 16 setTime( hr, min, sec ); // validate and set time 17 } // end Time constructor 18 19 // set new Time value using universal time; ensure that 20 // the data remains consistent by setting invalid values to zero 21 void Time::setTime( int h, int m, int s ) 22 { 23 setHour( h ); // set private field hour 24 setMinute( m ); // set private field minute 25 setSecond( s ); // set private field second 26 } // end function setTime 27 28 // set hour value 29 void Time::setHour( int h ) 30 { 31 hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour 32 } // end function setHour 33 34 // set minute value 35 void Time::setMinute( int m ) 36 { 37 minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute 38 } // end function setMinute 39 40 // set second value 41 void Time::setSecond( int s ) 42 { 43 second = ( s >= 0 && s < 60 ) ? s : 0; // validate second 44 } // end function setSecond 45 46 // return hour value 47 int Time::getHour() 48 { 49 return hour; 50 } // end function getHour 51 52 // return minute value 53 int Time::getMinute() 54 { 55 return minute; 56 } // end function getMinute 57 58 // return second value 59 int Time::getSecond() 60 { 61 return second; 62 } // end function getSecond 63 64 // print Time in universal-time format (HH:MM:SS) 65 void Time::printUniversal() 66 { 67 cout << setfill( '0' ) << setw( 2 ) << getHour() << ":" 68 << setw( 2 ) << getMinute() << ":" << setw( 2 ) << getSecond(); 69 } // end function printUniversal 70 71 // print Time in standard-time format (HH:MM:SS AM or PM) 72 void Time::printStandard() 73 { 74 cout << ( ( getHour() == 0 || getHour() == 12 ) ? 12 : getHour() % 12 ) 75 << ":" << setfill( '0' ) << setw( 2 ) << getMinute() 76 << ":" << setw( 2 ) << getSecond() << ( hour < 12 ? " AM" : " PM" ); 77 } // end function printStandard

In Fig. 9.9, line 16 of the constructor calls member function setTime with the values passed to the constructor (or the default values). Function setTime calls setHour to ensure that the value supplied for hour is in the range 023, then calls setMinute and setSecond to ensure that the values for minute and second are each in the range 059. If a value is out of range, that value is set to zero (to ensure that each data member remains in a consistent state). In Chapter 16, Exception Handling, we throw exceptions to inform the user that a value is out of range, rather than simply assigning a default consistent value.


Note that the Time constructor could be written to include the same statements as member function setTime, or even the individual statements in the setHour, setMinute and setSecond functions. Calling setHour, setMinute and setSecond from the constructor may be slightly more efficient because the extra call to setTime would be eliminated. Similarly, copying the code from lines 31, 37 and 43 into constructor would eliminate the overhead of calling setTime, setHour, setMinute and setSecond. Coding the Time constructor or member function setTime as a copy of the code in lines 31, 37 and 43 would make maintenance of this class more difficult. If the implementations of setHour, setMinute and setSecond were to change, the implementation of any member function that duplicates lines 31, 37 and 43 would have to change accordingly. Having the Time constructor call setTime and having setTime call setHour, setMinute and setSecond enables us to limit the changes to code that validates the hour, minute or second to the corresponding set function. This reduces the likelihood of errors when altering the class's implementation. Also, the performance of the Time constructor and setTime can be enhanced by explicitly declaring them inline or by defining them in the class definition (which implicitly inlines the function definition).

Software Engineering Observation 9.9

If a member function of a class already provides all or part of the functionality required by a constructor (or other member function) of the class, call that member function from the constructor (or other member function). This simplifies the maintenance of the code and reduces the likelihood of an error if the implementation of the code is modified. As a general rule: Avoid repeating code.

Software Engineering Observation 9.10

Any change to the default argument values of a function requires the client code to be recompiled (to ensure that the program still functions correctly).

Function main in Fig. 9.10 initializes five Time objectsone with all three arguments defaulted in the implicit constructor call (line 11), one with one argument specified (line 12), one with two arguments specified (line 13), one with three arguments specified (line 14) and one with three invalid arguments specified (line 15). Then the program displays each object in universal-time and standard-time formats.


Figure 9.10. Constructor with default arguments.

(This item is displayed on pages 497 - 498 in the print version)

1 // Fig. 9.10: fig09_10.cpp 2 // Demonstrating a default constructor for class Time. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include "Time.h" // include definition of class Time from Time.h 8 9 int main() 10 { 11 Time t1; // all arguments defaulted 12 Time t2( 2 ); // hour specified; minute and second defaulted 13 Time t3( 21, 34 ); // hour and minute specified; second defaulted 14 Time t4( 12, 25, 42 ); // hour, minute and second specified 15 Time t5( 27, 74, 99 ); // all bad values specified 16 17 cout << "Constructed with: t1: all arguments defaulted "; 18 t1.printUniversal(); // 00:00:00 19 cout << " "; 20 t1.printStandard(); // 12:00:00 AM 21 22 cout << " t2: hour specified; minute and second defaulted "; 23 t2.printUniversal(); // 02:00:00 24 cout << " "; 25 t2.printStandard(); // 2:00:00 AM 26 27 cout << " t3: hour and minute specified; second defaulted "; 28 t3.printUniversal(); // 21:34:00 29 cout << " "; 30 t3.printStandard(); // 9:34:00 PM 31 32 cout << " t4: hour, minute and second specified "; 33 t4.printUniversal(); // 12:25:42 34 cout << " "; 35 t4.printStandard(); // 12:25:42 PM 36 37 cout << " t5: all invalid values specified "; 38 t5.printUniversal(); // 00:00:00 39 cout << " "; 40 t5.printStandard(); // 12:00:00 AM 41 cout << endl; 42 return 0; 43 } // end main  

Constructed with: t1: all arguments defaulted 00:00:00 12:00:00 AM t2: hour specified; minute and second defaulted 02:00:00 2:00:00 AM t3: hour and minute specified; second defaulted 21:34:00 9:34:00 PM t4: hour, minute and second specified 12:25:42 12:25:42 PM t5: all invalid values specified 00:00:00 12:00:00 AM  


Notes Regarding Class Time's Set and Get Functions and Constructor

Time's set and get functions are called throughout the body of the class. In particular, function setTime (lines 2126 of Fig. 9.9) calls functions setHour, setMinute and setSecond, and functions printUniversal and printStandard call functions getHour, getMinute and getSecond in line 6768 and lines 7476, respectively. In each case, these functions could have accessed the class's private data directly without calling the set and get functions. However, consider changing the representation of the time from three int values (requiring 12 bytes of memory) to a single int value representing the total number of seconds that have elapsed since midnight (requiring only four bytes of memory). If we made such a change, only the bodies of the functions that access the private data directly would need to changein particular, the individual set and get functions for the hour, minute and second. There would be no need to modify the bodies of functions setTime, printUniversal or printStandard, because they do not access the data directly. Designing the class in this manner reduces the likelihood of programming errors when altering the class's implementation.

Similarly, the Time constructor could be written to include a copy of the appropriate statements from function setTime. Doing so may be slightly more efficient, because the extra constructor call and call to setTime are eliminated. However, duplicating statements in multiple functions or constructors makes changing the class's internal data representation more difficult. Having the Time constructor call function setTime directly requires any changes to the implementation of setTime to be made only once.

Common Programming Error 9.2

A constructor can call other member functions of the class, such as set or get functions, but because the constructor is initializing the object, the data members may not yet be in a consistent state. Using data members before they have been properly initialized can cause logic errors.

Категории