9.6 Time Class Case Study: Constructors with Default Arguments

The program of Figs. 9.59.7 enhances class Time to demonstrate how arguments can be passed to a constructor implicitly.

9.6.1 Constructors with Default Arguments

Like other functions, constructors can specify default arguments. Line 13 of Fig. 9.5 declares a Time constructor with default arguments, specifying a default value of zero for each argument passed to the constructor. The constructor is declared explicit because it can be called with one argument. We discuss explicit constructors in detail in Section 10.13.

Fig. 9.5 Time class containing a constructor with default arguments.

Alternate View

 1   // Fig. 9.5: Time.h
 2   // Time class containing a constructor with default arguments.
 3   // Member functions defined in Time.cpp.
 4   #include <string>
 5
 6   // prevent multiple inclusions of header
 7   #ifndef TIME_H
 8   #define TIME_H
 9
10   // Time class definition
11   class Time {
12   public:
13      explicit 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       unsigned int getHour() const; // return hour
23       unsigned int getMinute() const; // return minute
24       unsigned int getSecond() const; // return second
25
26       std::string toUniversalString() const; // 24-hour time format string
27       std::string toStandardString() const; // 12-hour time format string
28    private:
29       unsigned int hour{0}; // 0 - 23 (24-hour clock format)
30       unsigned int minute{0}; // 0 - 59
31       unsigned int second{0}; // 0 - 59
32    };
33
34    #endif

In Fig. 9.6, lines 11–13 define the Time constructor that receives values for parameters hour, minute and second that will be used to initialize private data members hour, minute and second, respectively. A constructor that defaults all its arguments is also a default constructor—that is, a constructor that can be invoked with no arguments. There can be at most one default constructor per class. The version of class Time in this example 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.

Software Engineering Observation 9.4

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).

Fig. 9.6 Member-function definitions for class Time.

Alternate View

 1   // Fig. 9.6: Time.cpp
 2   // Member-function definitions for class Time.
 3   #include <iomanip>
 4   #include <stdexcept>
 5   #include <sstream>
 6   #include <string>
 7   #include "Time.h" // include definition of class Time from Time.h
 8   using namespace std;
 9
10   // Time constructor initializes each data member          
11   Time::Time(int hour, int minute, int second) {            
12      setTime(hour, minute, second); // validate and set time
13   }                                                         
14
15   // set new Time value using universal time
16   void Time::setTime(int h, int m, int s) {
17      setHour(h); // set private field hour
18      setMinute(m); // set private field minute
19      setSecond(s); // set private field second
20   }
21
22   // set hour value
23   void Time::setHour(int h) {
24      if (h >= 0 && h < 24) {
25         hour = h;
26      }
27      else {
28         throw invalid_argument("hour must be 0-23");
29      }
30    }
31
32    // set minute value
33    void Time::setMinute(int m) {
34       if (m >= 0 && m < 60) {
35          minute = m;
36       }
37       else {
38          throw invalid_argument("minute must be 0-59");
39       }
40     }
41
42     // set second value
43     void Time::setSecond(int s) {
44        if (s >= 0 && s < 60) {
45           second = s;
46        }
47        else {
48           throw invalid_argument("second must be 0-59");
49        }
50      }
51
52      // return hour value
53      unsigned int Time::getHour() const {return hour;}
54
55      // return minute value
56      unsigned Time::getMinute() const {return minute;}
57
58      // return second value
59      unsigned Time::getSecond() const {return second;}
60
61      // return Time as a string in universal-time format (HH:MM:SS)
62      string Time::toUniversalString() const {
63         ostringstream output;
64         output << setfill('0') << setw(2) << getHour() << ":"
65            << setw(2) << getMinute() << ":" << setw(2) << getSecond();
66         return output.str();
67      }
68
69      // return Time as string in standard-time format (HH:MM:SS AM or PM)
70      string Time::toStandardString() const {
71         ostringstream output;
72         output << ((getHour() == 0 || getHour() == 12) ? 12 : getHour() % 12)
73            << ":" << setfill('0') << setw(2) << getMinute() << ":" << setw(2)
74            << getSecond() << (hour < 12 ? " AM" : " PM");
75         return output.str();
76    }

In Fig. 9.6, line 12 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 0–23, then calls setMinute and setSecond to ensure that the values for minute and second are each in the range 0–59. Functions setHour (lines 23–30), setMinute (lines 33–40) and setSecond (lines 43–50) each throw an exception if an out-of-range argument is received.

Function main in Fig. 9.7 initializes five Time objects—one with all three arguments defaulted in the implicit constructor call (line 15), one with one argument specified (line 16), one with two arguments specified (line 17), one with three arguments specified (line 18) and one with three invalid arguments specified (line 28). Each explicit constructor call (lines 16–18 and 28) uses C++11 list-initializer syntax. The program displays each object in universal-time and standard-time formats. For Time object t5 (line 28), the program displays an error message because the constructor arguments are out of range.

Fig. 9.7 Constructor with default arguments.

Alternate View

 1   // Fig. 9.7: fig09_07.cpp
 2   // Constructor with default arguments.
 3   #include <iostream>
 4   #include <stdexcept>
 5   #include "Time.h" // include definition of class Time from Time.h 
 6   using namespace std;
 7
 8   // displays a Time in 24-hour and 12-hour formats
 9   void displayTime(const string& message, const Time& time) {
10      cout << message << "
Universal time: " << time.toUniversalString()
11         << "
Standard time: " << time.toStandardString() << "

";
12   }
13
14   int main() {
15      Time t1; // all arguments defaulted                            
16      Time t2{2}; // hour specified; minute and second defaulted     
17      Time t3{21, 34}; // hour and minute specified; second defaulted
18      Time t4{12, 25, 42}; // hour, minute and second specified      
19
20      cout << "Constructed with:

";
21      displayTime("t1: all arguments defaulted", t1);
22      displayTime("t2: hour specified; minute and second defaulted", t2);
23      displayTime("t3: hour and minute specified; second defaulted", t3);
24      displayTime("t4: hour, minute and second specified", t4);
25
26      // attempt to initialize t5 with invalid values
27      try {
28         Time t5{27, 74, 99}; // all bad values specified
29      }
30      catch (invalid_argument& e) {
31         cerr << "Exception while initializing t5: " << e.what() << endl;
32      }
33   }

Constructed with:

t1: all arguments defaulted
Universal time: 00:00:00
Standard time: 12:00:00 AM

t2: hour specified; minute and second defaulted
Universal time: 02:00:00
Standard time: 2:00:00 AM

t3: hour and minute specified; second defaulted
Universal time: 21:34:00
Standard time: 9:34:00 PM

t4: hour, minute and second specified
Universal time: 12:25:42
Standard time: 12:25:42 PM

Exception while initializing t5: hour must be 0-23

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

Time’s set and get functions are called throughout the class’s body. In particular, function setTime (lines 16–20 of Fig. 9.6) calls functions setHour, setMinute and setSecond, and functions toUniversalString and toStandardString call functions getHour, getMinute and getSecond in lines 64–65 and lines 72–74.

In each case, these functions could have accessed the class’s private data directly. However, consider changing the representation of the time from three int values (requiring 12 bytes of memory on systems with four-byte ints) 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 change—in 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, toUniversalString or toStandardString, because they do not access the data directly.

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 call to setTime is eliminated. However, duplicating statements in multiple functions or constructors makes changing the class’s internal data representation more difficult. 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.

Software Engineering Observation 9.5

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

 

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 initialized. Using data members before they have been properly initialized can cause logic errors.

 

Software Engineering Observation 9.6

Making data members private and controlling access, especially write access, to those data members through public member functions helps ensure data integrity.

 

Error-Prevention Tip 9.4

The benefits of data integrity are not automatic simply because data members are made private—you must provide appropriate validity checking.

9.6.2 Overloaded Constructors and C++11 Delegating Constructors

Section 6.16 showed how to overload functions. A class’s constructors and member functions can also be overloaded. Overloaded constructors typically allow objects to be initialized with different types and/or numbers of arguments. To overload a constructor, provide in the class definition a prototype for each version of the constructor, and provide a separate constructor definition for each overloaded version. This also applies to the class’s member functions.

In Figs. 9.59.7, the Time constructor with three parameters had a default argument for each parameter. We could have defined that constructor instead as four overloaded constructors with the following prototypes:


Time(); // default hour, minute and second to 0
explicit Time(int); // init hour; default minute and second to 0
Time(int, int); // initialize hour and minute; default second to 0
Time(int, int, int); // initialize hour, minute and second

Just as a constructor can call a class’s other member functions to perform tasks, C++11 allows constructors to call other constructors in the same class. The calling constructor is known as a delegating constructor—it delegates its work to another constructor. This is useful when overloaded constructors have common code that previously would have been defined in a private utility function and called by all the constructors.

The first three of the four Time constructors declared above can delegate work to one with three int arguments, passing 0 as the default value for the extra parameters. To do so, you use a member initializer with the name of the class as follows:


Time::Time() : Time{0, 0, 0} {} // delegate to Time(int, int, int)

// delegate to Time(int, int, int)
Time::Time(int hour) : Time{hour, 0, 0} {}

// delegate to Time(int, int, int)
Time::Time(int hour, int minute) : Time{hour, minute, 0} {}
..................Content has been hidden....................

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