Previously in IC210 ...

We 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. We considered a program like this:

point.h point.cpp main0.cpp

struct point
{
  double x, y;
};
void read(point&);

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

void read(point& p)
{
  char c;
  cin >> c >> p.x
      >> c >> p.y >> c;  
}

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

int main()
{
  point p;
  read(p);
  cout << "dist is " 
		 << sqrt(p.x*p.x + p.y*p.y) << endl;
  return 0;
}
When a program consists of multiple .cpp files like this, you can compile each .cpp file separately, and then link, like this:
~/$ g++ -c point.cpp            compiles point.cpp to produce  point.o
~/$ g++ -c main0.cpp            compiles main0.cpp to produce main0.o
~/$ g++ -o ex0 main0.o point.o  links point.o and main0.co to produce executable ex0
... or you can compile and link it all at one go, like this:
~/$ g++ -o ex0 main0.cpp point.cpp
Large programs can take minutes or even hours to compile from scratch. When you compile separately, a change in one .cpp means only that .cpp needs to be recompiled - for the rest, the old .o files can be reused. So separate compilation is the way to go once projects start to grow.

The Rules in Short

C++ requires that any function be declared before it is used. I.e. a function's prototype must appear in the code prior to the first time the function is called. Note that "appear" could mean literally be present in the file, or present via a #include. C++ also requires that any struct be defined before it is used. I.e. the struct definition must appear in the code prior to the first time variables or parameters of that struct type occur. Once again, "appear" could mean literally be present in the file, or present via a #include. However, there are some rules about how often prototypes and struct definitions can appear.
  1. A function may only be defined once in a program, no matter how many files may be involved!
  2. A struct's definition may appear in as many files as you like, but only once per .cpp file after #include .h files have been inserted! Also, it must be the same in every file.
  3. Occurrences of what are otherwise the same prototype must agree in the return type. You can't, for example have the prototype bool f(int); in one place and string f(int); in another.
These are the solid C++ rules, here's the Golden Rule however: Every prototype or struct definition should exist only in one file. If you need that struct or function in several files, put the struct 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 problem that can come up, which is illustrated by the following example:

point.h point.cpp main1.cpp

struct point
{
  double x, y;
};
void read(point&);

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

void read(point& p)
{
  char c;
  cin >> c >> p.x
      >> c >> p.y >> c;  
}

#include "point.h"
#include "triangle.h"

int main()
{
  triangle T;
  read(T);
  point P = T.vertex[0];
  return 0;
}
triangle.h triangle.cpp

#include "point.h"
struct triangle
{
  point vertex[3];
};
void read(triangle&);

#include "triangle.h"

void read(triangle& t)
{
  for(int i = 0; i < 3; i++)
    read(t.vertex[i]);
}
Now look what happens when we try to compile:
$ g++ -c point.cpp
$ g++ -c triangle.cpp
$ g++ -c main1.cpp
In file included from triangle.h:1:0,
                 from main1.cpp:2:
point.h:1:8: error: redefinition of 'struct point'
point.h:1:8: error: previous definition of 'struct point'
The problem is that this causes the struct 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 struct 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 structs 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". 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.h triangle.h main.cpp

#ifndef POINTHEADER
#define POINTHEADER

struct point
{
  double x, y;
};
void read(point&);

#endif

#include "point.h"
struct triangle
{
  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 single class period, but we're going to focus on the big-picture structure of the program (i.e. what structs, functions and files) rather than on the details of how each function actually works. Here's the problem: Write a 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 committee 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 committee 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 minutes 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/14
New Chairman Brown wrested control of the Committee from the senior faculty!
endminutes
save comm.txt
quit
CCM110814.txt
8 November, 2014

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

Committee Members:
Ric Crabbe
Dan Roche
Gavin Taylor
Don Needham

Minutes:
New Chairman Brown wrested control of the Committee from the senior faculty!
comm.txt
Committee on Confusing Midshipmen
Chris Brown
Ric Crabbe
Dan Roche
Gavin Taylor
Don Needham
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 committees. 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 structs, we say "I wish I had a struct 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;
      cin >> name;
      ofstream fout(name.c_str());
      write(C,fout);
    }
  }

  return 0;
}
Clearly what we need to do is go off and give the struct 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 struct 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 committee 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 fashion, whose .cpp file might look like Member.cpp) We'll define Committee as:

struct Commitee
{
  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 struct Member comes from the Member.h header file, so our Committee.h header file looks like

/***************************************
 ** Header file for struct Commitee and 
 ** associated functions
 ***************************************/
#include "Member.h"

#ifndef COMMITTEEHEADER
#define COMMITTEEHEADER

struct Committee
{
  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 modify the committee to make a new 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 struct - 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

  1. 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 struct 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;
    }
    A run of the program should look like this:
    Enter boat info: Slowpoke (140.3,33.7) 37.0 degrees 10 mph
    Currently: Slowpoke (140.3,33.7) 37 degrees 10 mph
    Time elapsed in hours: 0.5
    Currently: Slowpoke (140.601,34.0993) 37 degrees 10 mph
    Time elapsed in hours: 2.5
    Currently: Slowpoke (142.105,36.0959) 37 degrees 10 mph
    Time elapsed in hours: quit
    
    Requirements: You must make a struct 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!