Lab 4. Classes and Objects: A Time Class

Introduction

So far, every program you have written has been a collection of functions operating on loose variables. A
class lets you do something different: bundle a set of related variables together with the functions that
work on them, and hand the result out as a new data type of your own.

You have been using classes all semester without building one. string is a class. When you write
s.length(), you are calling a member function on a string object — the same syntax you are about to
write for yourself.

In this lab you will build a class that represents a moment of time in the 24-hour format, and by the end of
it you will be able to write:

Time lecture(10, 30);
Time lunch(13, 40);

cout << lecture.minutesUntil(lunch) << " minutes of class left" << endl;

A class is a blueprint; objects are what you build from it

The class declaration itself does not create anything. It describes what a Time is — what values it holds
and what you can do with it. Each variable you then declare of that type is an object, or an instance
of the class, with its own private copy of the data.

One class, many objects THE CLASS — A BLUEPRINT class Time int hour; int minute; minutesUntil() addMinutes() Describes what a Time is. Holds no values itself. build OBJECTS — INSTANCES OF IT lecture 10:30 lunch 13:40 alarm 6:45 Three objects, each with its own hour and minute. Changing one has no effect on the others. lecture.addMinutes(75); calls a member function on one particular object

The shape of a class declaration

Here is the skeleton you will be filling in. Note the semicolon after the closing brace — it is required,
and leaving it off produces a spectacularly confusing error message.

class Time
{
    private:
        int hour;
        int minute;
    public:
        bool setHour(int h);
        int getHour();
};

Two access specifiers divide the class:

Bundling data together with the functions that operate on it is called encapsulation. Making the data
private so that it can only be reached through those functions is called data hiding. They are related
but not the same idea, and it is worth keeping the two words straight.

Private data is reachable only through the public interface class Time PRIVATE hour minute PUBLIC INTERFACE setHour() getHour() minutesUntil() addMinutes() CLIENT CODE t.hour = 25; compiler error t.setHour(25); allowed — and checked The wall is the point: every change to the data has to pass through a function you wrote.

Where the member functions go

You declare a member function inside the class, and define it below, outside the class. In the definition,
the function’s name is prefixed with the class name and the scope resolution operator ::, which is how
the compiler knows the function belongs to Time rather than being an ordinary free function:

/**********************************************************
 * Time::getHour
 * Returns the hour stored in this Time object.
 **********************************************************/
int Time::getHour()
{
    return hour;
}

Notice that getHour uses hour with no object in front of it. A member function is always working on some
particular object — the one it was called on — and it reaches that object’s member variables directly.

The class name and :: are part of the function’s name, so they go immediately before the function name,
after the return type. int Time::getHour() is correct; Time::int getHour() is not.

Very short member functions may instead be written inline, with the body right inside the class
declaration. That is fine for one-liners and gets unwieldy for anything longer.


Task A. Declaring the class

Write a program time.cpp containing a class Time that represents a moment in the 24-hour format.

Private member variables:

int hour;      // 0 - 23
int minute;    // 0 - 59

Public member functions, to start with:

void setHour(int h);
void setMinute(int m);
int  getHour();
int  getMinute();

Functions like setHour that store a value into a member variable are called mutators (or set functions).
Functions like getHour that report a value without changing anything are called accessors (or get
functions).

Write a main that creates two Time objects, sets them to different values, and prints both back out using
the accessors. Then, to see the wall for yourself, add the line

lecture.hour = 9;

and try to compile. Read the error message carefully — this is the compiler enforcing data hiding, and you
should recognize the message when you meet it again by accident. Then delete the line.

Task B. Mutators that refuse bad data

Right now nothing stops a caller from writing setHour(37). Since the whole reason the data is private is so
that the class can protect it, let’s actually protect it.

Change both mutators to report whether they accepted the value:

bool setHour(int h);
bool setMinute(int m);

Each should store the value and return true if it is in range, and otherwise leave the member variable alone
and return false. Now the caller can tell whether the operation worked:

if (!lecture.setHour(37))
    cout << "Invalid hour." << endl;

This is the same pattern you have seen in lecture in Rectangle::setLength and in Account::withdraw: an
operation that can fail returns a bool, and the client decides what to do about it. Notice what the
class does not do — it does not print an error message itself. Keeping input and output out of the class is
a habit worth starting now, because it is what lets the same class be used by a program that has no screen at
all.

Update main to try several invalid values and confirm they are rejected.

Task C. Constructors

There is still a hole. When you write Time lecture;, the object exists immediately, but hour and minute
hold whatever garbage happened to be in that memory. Nothing forces the caller to set them.

A constructor is a special public member function that runs automatically when an object is created. Two
rules define it: it has the same name as the class, and it has no return type at all — not even
void.

Add two constructors to Time:

Time();                     // default constructor: sets 0:00
Time(int h, int m);         // sets the given time

Define them below the class, the same way as any other member function — the class name now appears twice:

Time::Time()
{
    hour = 0;
    minute = 0;
}

The two-parameter version should use your mutators rather than assigning hour and minute directly, so
that the validation you wrote in Task B applies to construction too. Decide what it should do with an invalid
argument, and write a comment saying what you chose.

The parentheses rule

This trips up everyone once:

Time lecture(10, 30);   // calls the two-parameter constructor
Time midnight;          // calls the default constructor
Time alarm();           // WRONG - this is not an object at all

The third line declares a function named alarm that takes nothing and returns a Time. When you want the
default constructor, there are no parentheses.

Test all three forms in main (the third will produce a strange error when you try to use alarm — look at
it once, then remove it).

Task D. Computed values, and the ones you should not store

Add a member function that reports how many minutes have passed since midnight:

int minutesSinceMidnight();

For 10:30 it returns 630.

Here is the design question worth pausing on. You could instead add a third member variable, int totalMinutes, and update it every time the hour or minute changes. Don’t. The moment you store a value
that can be derived from other values, you have two things that must be kept in agreement, and sooner or
later some code path updates one and forgets the other. That is called stale data, and the cure is to
compute the value fresh on every call rather than storing it.

Your class should store only hour and minute. Everything else is calculated on demand.

Task E. Passing objects to functions

Add a free function — not a member of the class — that prints a Time:

void printTime(Time t);

It should print in H:MM format, so that 9:05 prints as 9:05 and not 9:5. (Getting the leading zero on
the minutes right is part of the task.) It takes an object as an argument just like any other value, and
reaches the data through the accessors, because hour and minute are private and printTime is not a
member function.

Passing by value copies the whole object

printTime(Time t) receives a copy of the object — every member variable is duplicated. For a Time
that is two integers and nobody cares. For a larger class it is real work, and the copy is thrown away
immediately.

The alternative is to pass a constant reference: no copy is made, and the const promises the function
will not modify the caller’s object.

void printTime(const Time &t);
A copy of the object, or a read-only view of it printTime(Time t) BY VALUE main's lecture hour 10 minute 30 the copy, t hour 10 minute 30 Every member variable is duplicated, then discarded when the function returns. printTime(const Time &t) BY CONSTANT REFERENCE main's lecture hour 10 minute 30 t No copy at all — t is the caller's object. The const forbids changing it. But now the accessors it calls must be marked const too.

There is a catch, shown in red above. Through a const reference the compiler will not let you call any
member function that it cannot prove is harmless — including your accessors. To make an accessor usable on a
constant object, mark it const by putting the keyword after the parameter list, in both the declaration and
the definition:

int getHour() const;        // in the class declaration

int Time::getHour() const   // in the definition
{
    return hour;
}

Do this for getHour, getMinute, and minutesSinceMidnight — every member function that only reads.
Leave the mutators alone; they are supposed to modify the object.

Task F. Objects as arguments and return values

Add two more member functions.

int minutesUntil(const Time &later) const;

returns how many minutes separate this time from later:

Time morning(10, 30);
Time afternoon(13, 40);
morning.minutesUntil(afternoon)     // returns 190

If later is actually earlier, return a negative number. If minutesSinceMidnight is correct, this function
is one line.

Note that the object appears on both sides here: morning is the object the function was called on, and
afternoon is an object passed to it. Inside the function, hour and minute mean the caller’s, and
later.getHour() reaches the argument’s.

Time addMinutes(int mins) const;

returns a new Time object, mins minutes later than this one:

Time class1(8, 10);
Time class2 = class1.addMinutes(75);    // class2 is 9:25, class1 is unchanged

A function can return an object exactly as it returns an int. Build the result, return it, and leave the
original alone — which is why this one is const too. You may assume the result stays within the same day.

Test both in main with printTime.

Task G (Bonus). A private helper, and a destructor

A private member function. Both addMinutes and your constructor probably need to turn a number of
minutes since midnight back into an hour and a minute. That is an implementation detail — no user of the
class should ever call it — so declare it in the private section:

private:
    void setFromMinutes(int total);

Private member functions are how a class keeps its public interface small while still breaking its own work
into pieces. The rule is the same one you have been applying all semester; only the audience changes.

A destructor. Just as a constructor runs when an object is created, a destructor runs when it is
destroyed. Its name is the class name with a tilde in front, it takes no arguments ever, and a class can have
exactly one:

Time::~Time()
{
    cout << "A Time object is going away." << endl;
}

Add it, run your program, and watch when the messages appear. Then explain in a comment why you see a
destructor message immediately after the copy made by printTime(Time t) is finished with — and why that
message disappears if you switch to const Time &t.

(A destructor that only prints a message is not doing anything useful yet. It becomes essential in Chapter 10,
when objects start owning memory that has to be handed back.)

How to submit your program

Submit through Gradescope

This lab builds one program, time.cpp, which grows with each task. Submit the final version.

Your program should start with a comment that contains your name and a short program description, for example:

/*
Author: your name
Course: CSCI-135
Instructor: their name
Assignment: Lab4

Here, briefly, at least in one or a few sentences
describe what the program does.
*/