11.8.1. Time Example (Overloaded Operators Version)

Time: 00:3:13 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)

Review

The overloaded operator version of the Time example continues the evolution of Time that began as a structure, was converted to a class with member functions, and now is given overloaded operators. It still consists of three files, each requiring modification to complete the conversion to overloaded operators. Two versions of the example follow: The first version uses two member functions to implement the addition operation. In contrast, the second version uses only a single friend (i.e., non-member) function to perform the addition operation.

operator+ As a Member Function

UML class diagram for the Time class:
Time
----------------------
-hours : int
-minutes : int
-seconds : int
----------------------
+Time()
+Time(h : int, m : int, s : int)
+Time(s : int)
+operator+(t2 : Time) : Time
+operator+(i : int) : Time
+operator<<(out : ostream&, t : Time&) : ostream&
+operator>>(in : istream&, t : Time&) : istream&
The Time UML class diagram with overloaded operators.

The following figure illustrates one way programmers can convert the UML class diagram to a C++ class.

#include <iostream>									// (a)
using namespace std;

class Time
{
    private:
	int	hours = 0;								// (b)
	int	minutes = 0;
	int	seconds = 0;

    public:
		Time() {}								// (c)
		Time(int h, int m, int s) : hours(h), minutes(m), seconds(s) {}		// (d)
		Time(int s);

	Time	operator+(Time t2);							// (e)
	Time	operator+(int i);

	friend	ostream& operator<<(ostream& out, Time& t);				// (f)
	friend	istream& operator>>(istream& in, Time& t);				// (g)
};
Time.h (operator version).
  1. The previous version of the Time header file didn't reference any external (outside the Time class) classes or functions and, therefore, didn't require an include directive. However, the overloaded-operator version of Time replaces the print and read functions with the inserter and extractor operators, which use stream objects declared in the iostream header.
  2. The member variables are initialized to 0 (red) in this example
  3. Initializing the member variables in the class specification leaves no work for the default constructor; nevertheless, the constructor is necessary for clients to create empty Time objects
  4. The second and third constructors remain unchanged from the Chapter 9 example
  5. Two overloaded versions of operator+, distinguished by their unique argument lists (Figure 2), replace the add function:
    • The first operator+ permits operations of the form Time + Time
    • The second operator+ permits operations of the form Time + int
    • This version of the Time doesn't support reversing the operands: int + Time
  6. operator<<(ostream& out, Time& t) replaces the print function
  7. operator>>(istream& in, Time& t) replaces the read function
#include "Time.h"					    // (a)
#include <iostream>
#include <iomanip>
using namespace std;

Time::Time(int s)					    // (b)
{
    hours = s / 3600;
    s %= 3600;        // s = s % 3600;
    minutes = s / 60;
    seconds = s % 60;
}

Time Time::operator+(Time t2)				    // (c)
{
    int i1 = hours * 3600 + minutes * 60 + seconds;
    int i2 = t2.hours * 3600 + t2.minutes * 60 + t2.seconds;

    return Time(i1 + i2);
}

Time Time::operator+(int i)				    // (d)
{
    int    i1 = hours * 3600 + minutes * 60 + seconds;

    return Time(i1 + i);
}
ostream& operator<<(ostream& out, Time& t)	// (e)
{
    out.fill('0');

    out << t.hours << ":"
        << setw(2) << t.minutes << ":"
        << setw(2) << t.seconds;

    out.fill(' ');

    return out;
}

istream& operator>>(istream& in, Time& t)	// (f)
{
    cout << "Please enter the hours: ";
    cin >> t.hours;

    cout << "Please enter the minutes: ";
    cin >> t.minutes;

    cout << "Please enter the seconds: ";
    cin >> t.seconds;

    return in;
}
 
Time.cpp (operator version).
  1. The Time class uses features declared in three header files. Although The Time.h included iostream, accepted programming style suggests including it here (see the highlighted comment in Robust vs. error-prone header files)
  2. The conversion constructor is unchanged from the earlier version
  3. The body of operator+ is identical to the body of the previous add, but the function header (Figure 2) has changed
  4. Adds an instance of time and an integer. The function is short enough to inline in the class specification:
    Time operator+(int i)
    {
        return Time(hours * 3600 + minutes * 60 + seconds + i);
    }
  5. operator<< replaces the print function. Formats the output, printing the hours and seconds with two digits with a leading 0 is needed
  6. operator>> replaces the read function. With the embedded prompts, this version is only appropriate for reading input from the console. The next figure presents a more general version

The read function introduced in chapter 5 and modified for chapter 6 could only read from the console. It did not have a parameter allowing it to read input from other locations, so it was appropriate to include the prompts in the function. The inserter presented above retained the prompts, minimizing the changes between versions. However, operator<< does include a parameter (the first one) denoting the data source. We can write a program that uses the operator interactively by asking an end-user to input data. However, we can also write an unattended program that reads input from a file. In that case, there is no one to see and respond to the prompt, and the prompt can slow the program. I recommend moving the prompts to the application code (the client that uses the Time class) and adapting them as necessary. The following version of operator>> and the driver below illustrate this approach.

istream& operator>>(istream& in, Time& t)
{
	in >> t.hours;
	in >> t.minutes;
	in >> t.seconds;

	return in;
}
Alternate operator>>. This version of the extractor function is more general (i.e., it is usable in more situations) without the prompts.

A simple driver program demonstrating the Time class and its functions.

#include <iostream>
#include "Time.h"
using namespace std;


int main()
{
	Time	t;
	cout << "Enter the hours, minutes, and seconds ";
	cout << "(press \"Enter\" after each entry):" << endl;
	cin >> t;

	cout << t << endl;

	Time	s(1, 30, 4);
	cout << s << endl;

	Time	u = t + s;
	cout << u << endl;

	return 0;
}
driver.cpp.
Although programs can call overloaded operators using a functional notation, operator>>(cin, t) for "friends" or t.operator+(s) for members, there is no reason for doing so - if we wanted to use functional notation, we would continue using function names like read, print, and add. In practice, programmers exclusively call overloaded operators with an operator notation:
  • t + s
  • cin >> t
  • cout << s

operator+ As a friend Function

The two operator+ functions defined in the example above can support two similar but distinct function calls:

  1. Time + Time
  2. Time + int

Mathematically, int + Time is also valid, but this function call does not match the argument list of either overloaded operator+ function. Furthermore, it is not possible to write a version of operator+ as a member function allowing an integer as the first argument (Non-member overloaded operators). Fortunately, we can write an appropriate operator+ function as a non-member friend function. Furthermore, in conjunction with a conversion constructor, all three calling sequences may be satisfied with a single operator+ function (A single friend overloaded operator+).

UML class diagram for the Time class:
Time
----------------------
-hours : int
-minutes : int
-seconds : int
----------------------
+Time()
+Time(h : int, m : int, s : int)
+Time(s : int)
+operator+(t1 : Time, t2 : Time) : Time
+operator<<(out : ostream&, t : Time&) : ostream&
+operator>>(in : istream&, t : Time&) : istream&
The Time UML class diagram with a friend (non-member) operator+. This version of the UML Time class diagram replaces the two operator+ member functions with a single friend function. The "dog-eared" box is an optional UML feature allowing designers to annotate class diagrams. Its use here suggests that C++ programmers must implement the function as a friend; otherwise, the only indication is the number of parameters.
class Time
{
    private:
	int	hours;
	int	minutes;
	int	seconds;

    public:
		Time() : hours(0), minutes(0), seconds(0) {}
		Time(int h, int m, int s) : hours(h), minutes(m), seconds(s) {}
		Time(int s);

	friend	Time operator+(Time t1, Time t2);

	friend	ostream& operator<<(ostream& out, Time& t);
	friend	istream& operator>>(istream& in, Time& t);
};
Time.h. The single friend operator+ replaces the two corresponding member functions. Note that the friend keyword only appears in the class specification, not the function definition.
Time operator+(Time t1, Time t2)
{
	int	i1 = t1.hours * 3600 + t1.minutes * 60 + t1.seconds;
	int	i2 = t2.hours * 3600 + t2.minutes * 60 + t2.seconds;

	return Time(i1 + i2);
}
friend operator+. The friend version of the addition operator requires two explicit parameters because it is not bound to an object by the this pointer. The figure shows it as it appears in a source code file (i.e., not in the class specification). The function's header does not include Time:: because it isn't a member function.