SI204 Class 35: Breaking Programs Into Pieces

Homework
You will write a simple program to track ship movement over time. To help you out, I will provide you with a class Point defined in dbpoint.h and dbpoint.cpp. You can use this point class to track position.

Your program will read from the user info on a single boat. Specifically, you will be given its name, position (as an (x,y) pair giving its position in miles from some fixed point), its heading (in degrees from north) and its speed in miles per hour. It will then read a number of hours from the user and update the boat's position after that number hours. It will keep reading number of hours and printing over and over for as long as the user wants.

This function might also help you:

Point move(Point position, double heading, double speed, double t)
{
  double PI = 3.14159;
  Point d;
  d.x = t*speed*sin(heading*PI/180);
  d.y = t*speed*cos(heading*PI/180);
  return position + d;
}
      
Enter boat info: Slowpoke (140.3,33.7) 37.0 degrees 10 mph
Currently: Slowpoke (140.3,33.7) 37 degrees 10 mph
Time ellapsed in hours: 0.5
Currently: Slowpoke (140.601,34.0993) 37 degrees 10 mph
Time ellapsed in hours: 2.5
Currently: Slowpoke (142.105,36.0959) 37 degrees 10 mph
Time ellapsed in hours: quit
Requirements: You must make a class Boat that stores all the necessary information about a boat, and make all the work (like reading and writing and moving the boat) into functions. There should be a file Boat.h and Boat.cpp that implements all of this stuff. Your main function should be very simple, and should just include Boat.h. If you do this right, it would be trivial to transform this program so that it would track many boats simultaneously!

Turn In: A screen capture of your program running, and a printout of Boat.h, Boat.cpp and main.cpp.

Lecture
Last lecture we basically saw how programs get broken up into pieces using using .h files and #include. This lecture we'll look into more detail about how that's done, the organization benefits of doing it, and a few of the pitfalls.

The Rules in Short

  1. A function may only be defined once in a program, no matter how many files may be involved!
  2. A function's prototype may appear in as many files as you like, but only once per file! Also. occurences of the same prototype in different files must agree. You can't, for example have the prototype bool f(int); in one place and string f(int); in another.
  3. A class's definition may appear in as many files as you like, but only once per file! Also, it must be the same in every file.

These are the solid C++ rules, here's the Golden Rule however: Every prototype or class definition should exist only in one file. If you need that class or function in several files, put the class definition or prototype in a .h file and use #include to put them in other files. This is wisdom, pay attention to it!

Now, there's one trick that can come up, which is illustrated by the following example:
point.htriangle.hmain.cpp
class point
{
public:
  double x, y;
};
void read(point&);
	    
#include "point.h"
class triangle
{
public:
  point vertex[3];
};
void read(triangle&);
	    
#include "point.h"
#include "triangle.h"

int main()
{
  triangle T;
  read(T);
  point P = T.vertex[0];
}
	    
The trick is that this causes the class point to be defined twice in main.cpp. Why? Well, point.h includes the definition, and point.h is #included in main.cpp, so that's the first definition of point in main. However, main also #includes triangle.h, which in turn #includes point.h, which has the definition of the class point, so that's the second definition of point in main. Admittedly, if the programmer was on top of things he could've avoided this, but it's not always avoidable, and it's expecting alot of the guy who uses classes point and triangle to go back and worry about this stuff.

The way around this problem is to guard your .h files with #defines. The #include that we've been using and the #define that we're about to use are examples of "preprocessor directives". Pages 13-17 in your book talk about them in more detail. The #define lets you give a string x and say "every time you see x in the source code, replace it with string y." This can be used and abused in all sorts of ways, but for the moment we'll simply use #define to determine whether or not this is the first time the compiler has seen our .h while compiling a give .cpp file. We can test whether or not a certain string has been #defined already using the #ifndef preprocessor directive, which we read as "if not defined". For example

  cout << "This is ";
#ifndef SILLYSTYRING
  cout << "not ";
#endif
  cout << "much fun!" << endl;
If SILLYSTRING has been #defined previously in our program, the word not won't get printed out --- the #ifndef SILLYSTRING asks if it's true that SILLYSTRING hasn't been defined. If it is, then all the text until the #endif is seen by the compiler. Otherwise, as in our case, it is as if the code in between was never there.

How does this help us with our multiple-definition problem?
point.htriangle.hmain.cpp
#ifndef POINTHEADER
#define POINTHEADER

class point
{
public:
  double x, y;
};
void read(point&);

#endif
	    
#include "point.h"
class triangle
{
public:
  point vertex[3];
};
void read(triangle&);
	    
#include "point.h"
#include "triangle.h"

int main()
{
  triangle T;
  read(T);
  point P = T.vertex[0];
}
	    
Now, in compiling main.cpp we first #include the file point.h. Since this is the first time the compiler has seen point.h it'll find that POINTHEADER has not been defined. Therefore all the code until the #endif, which is essentially the whole file, will be seen and evaluated by the compiler. This will include the line that #defines POINTHEADER. Now, the next time we come across point.h, which is when main.cpp does the #include "triangle.h", the compiler will hit that #ifndef line and will find that POINTHEADER has been defined, and therefore everything up to the #endif, which is essentially the whole file, will be ignored by the compiler. This way we never get any multiple definitions.

Golden rule number 2: All .h files should be "protected" by #ifndefs this way!

A Problem to focus on

We'll start with a problem to focus on as a goal. It's certainly too big to write in total in a class period, but we're going to focus on the big-picture structure of the program (i.e. what classes, functions and files) rather than on the details of how each function actually works. Here's the problem: Write a program program that produces meeting reports. The program will allow the user to:
  1. Read in committee info, either from the user or from a file A committee will consist of 5 members, one of which is designated Committee Chair, and of course the committee has a name! You may assume that each committee member has a two-word name. The user command is load source, where source is either keyboard or a filename, depending on whether the user will enter the committee info by hand or read it from a file. From a file or from the user, the format should be committee name on the first line, committee chair name on the second line, and the other 4 committee member names on the following lines.
  2. Change Committee Chair The user should be able to name a new Chair from amongst the comittee members. The user command is makechair first last, where first last is the name of the new chair.
  3. Print a committee minutes to a file The user command is writeminutes destination date minutes endminutes The minutes should be written to the file destination, and should begin with the date, the comittee name, the chair, and the rest of the committee in alphabetical order. Dates should be input in mm/dd/yyyy format, but should appear in the minuts as <day> <monthname>, <year>
  4. Save committee info to a file The user command should be save filename The file saved should be in the proper format to read in the next time.
  5. Quit

So, suppose we have the following data file comm.txt, which contains a definition of the very powerful and important "Committee on Confusing Midshipmen". This run of the program along with the minutes file it creates, and the new version of comm.txt it makes.
Program:
load comm.txt
makechair Chris Brown
writeminutes CCM110802.txt 11/08/02
New Chairman Brown wrested control of the Committee from the senior faculty!
endminutes
save comm.txt
quit
CCM110802.txt
8 November, 2

Minutes of the Committee on Confusing Midshipmen
Chris Brown, Committee Chair

Committee Members:
Ric Crabbe
Margeret MacMahon
Don Needham
Kay Schulze

Minutes:
New Chairman Brown wrested control of the Committee from the senior faculty! 
comm.txt
Committee on Confusing Midshipmen
Chris Brown
Kay Schulze
Don Needham
Margeret MacMahon
Ric Crabbe

Admittedly, this is a big problem! We'll need all of our problem solving techniques --- top-down design, bottom-up design, iterative refinement. They all work together, and breaking code up into files helps facilitate all of it.

Part 1 - Reading & Writing Committees

Let's use the iterative refinement technique of starting out with a program that offers only a part of the functionality required of the final product. Let's write a program that does nothing but read and write comittees. In other words, let's just implement the load and save commands. Now using the top-down design technique in which we make a "wishlist" of functions and classes, we say "I wish I had a class Committee that stored all the info for a committee, and functions read and write for reading and writing committees, both to-and-from files and to-and-from the screen". If I had such things, perhaps in a file Committee.h, I could write my main like this:
#include "Committee.h"

int main()
{
  Committee C;
  string command;

  while(cin >> command && command != quit)
  {
    ////// LOAD COMMAND //////
    if (command == "load")
    {
      string source;
      cin >> source;
      if (source == "keyboard")
	read(C,cin);
      else
      {
	ifstream fin(source.c_str());
	read(C,fin);
      }
    }

    ///// SAVE COMMAND /////
    if (command == "save")
    {
      string name;
      ofstream fout(name.c_str());
      write(C,fout);
    }
  }

  return 0;
}
Clearly what we need to do is go off and give the class definition and function prototypes in a file Commitee.h, and later we can get around to actually implementing the functions in a file Committee.cpp. Let's focus on defining the class Committee first, since this is needed for the two prototypes. Well ... a committee has a name, which we'll store in a string, and it has five comittee members, one of whom is the Chair. A committee member has a first and a last name, so there's no built-in type to represent that for me. So, I say "I wish there was a type Member that could store a first and a last name for me ... and I'll probably want read and write functions for this type, as well." Well, let's assume there is! (It's .h file might look like Member.h and, in bottom-up fahion, whose .cpp file might look like Member.cpp) We'll define Committee as:
class Commitee
{
public:
  string name;
  Member chair, mems[4];
};
So that a Committee consists of a name, a chair and 4 members. Additionally we want functions for reading and writing, whose prototypes would be:
void read(Member&,istream&);
void write(Member,ostream&);
Now, the class Member comes from the Member.h header file, so our Committee.h header file looks like
/***************************************
 ** Header file for class Commitee and 
 ** associated functions
 ***************************************/
#include "Member.h"

#ifndef COMMITTEEHEADER
#define COMMITTEEHEADER

class Committee
{
public:
  string name;
  Member chair, mems[4];
};

void read(Committee&,istream&);
void write(Committee,ostream&);

#endif
Putting the whole program together, we have:
Member.h ----------------------> Member.cpp
  |                                |
  |                                |
  V                                |
Committee.h ---> Committee.cpp     |
  |                  |            /
  |                  |           /
  V                  |          /
main.cpp             |         /
   \                 |        /
    \                |        |
     \               V        V
      ------------> THE PROGRAM

Part 2 - Switching Chairs

Adding the functionality to switch chairs is nice, because it's natural to do this in main by simply adding
    if (command == "makechair")
    {
      Member M;
      read(M,cin);
      makechair(C,M);
    }
In other words, you read in a member and say "I wish there were a function makechair that could modifiy the committee to make a ne member the chair" ... and then assume there is! The prototype for makechair would naturally be added to Committee.h and the definition of the function would naturally reside in Committee.cpp. The structure of the program in terms of files looks just like it did before.

Part 3 - Printing Reports, the last step

The only command we're missing now is "writeminutes". This one will take a bit of work, because now in addition to dealing with "Committees" and "Members" we have to deal with "Dates". So, the natural thing to do is create yet another class - this one for "Date" objects. Naturally, this will go in its own .h file and have its own .cpp file as well. In main.cpp we'll add the code for our next command, which will "assume" we have a function "writeminutes", because gee wouldn't it be easier if we did?
    if (command == "writeminutes")
    {
      string name;
      cin >> name;
      ofstream fout(name.c_str());
      writeminutes(C,fout);
    }
Of course, now we have to go and implement writeminutes and create Date.h and Date.cpp. When we do, we end up with a program that, on the file level, is structured like this:
Member.h ---------------------------------> Member.cpp
  |                                            |
  |                Date.h ----> Date.cpp       |
  |                  |            |            |
  V                  V            |            |
Committee.h ---> Committee.cpp    |            |
  |                  |            |            /
  |                  |            |           /
  V                  |            |          /
main.cpp             |           /          /
   \                 |          /          /
    \                |         /          /
     \               V        \/         /  
      ------------> THE PROGRAM <--------

Problems

To Appear

Asst Prof Christopher Brown
Last modified: Tue Nov 19 09:16:24 EST 2002