CS401 - Separate Compilation in C++

UPDATED ON 7/14 at 7pm 

Why use separate compilation?

When you write a class you are writing a module that needs to be reusable.  Certainly, the most direct way to reuse code would be to cut and paste it wherever you find that you need it.  Unfortunately, this is unwieldy and sometimes it isn't clear what should be copied and what should be left behind.  The correct approach is to employ separate compilation by writing your class, compiling it apart from your program that uses the class.

You've seen how to write and use the Date class in one monolithic file. The overall layout looks like this:
 


compiler directives

class header

main() {

  declare Date objects
  use Date objects

}

method definitions
 

figure 1

This works, but the problem (as stated earlier), if there is another program where Date objects would be useful, it's a pain to do so.
 

What is separate compilation?

The solution is to define what a Date is in a different file from where it is used.  We will reorganize the Date class in this way.  The first thing we need to do is write a "header" file (ending in .h) for the class:
 
// date.h
// this is the header file for the Date class

#ifndef DATE_H_
#define DATE_H_

class Date {
   private:
     int month, day, year;
   public:
     void set(int,int,int);  // m,d,y
     void print();
     void read();
};

#endif

figure 2

The reason we need the conditional compilation directives is because the preprocessor needs to know that if the Date header file is included from some other module in the program, then it doesn't need to do this declaration again (in fact, it would be an error to do it twice).  See section 5.2.2 in your book for more information on how this works. 

The header file is much like a function prototype (in fact, it contains three function prototypes, if you look closely you'll see them) because it provides enough information to use Dates, but that's it.  It does not contain the code for how the class operates.  That needs to be done in a different file:
 

// date.cpp
// this file contains the method definitions for the date class

#include <iostream>
#include "date.h"

// this sets a Date object to a given date
void Date::set(int m, int d, int y) {
   month = m;
   day = d;
   year = y;
}

// this method displays the Date on the screen
void Date::print() {
   cout << month << '/' << day << '/' << year;
}

// this reads a Date in from cin
void Date::read() {
   cout << "Enter the month: ";
   cin >> month;
   cout << "Enter the day: ";
   cin >> day;
   cout << "Enter the year: ";
   cin >> year;
}

figure 3

This file contains the "guts" of the Date class, in that all of the methods are defined here.  One property of a well-written class is that someone who wants to use the class should not need to see this file.  In other words, everything we need to know to use Dates should be present in the header file (figure 2).

Notice how the header file is included here?  As you probably remember, the preprocessor will do a direct substitution of the header file above (date.h) and put it right where you see the #include directive.  We also include <iostream> because of the use of cin and cout, and the out and extraction operators.  Sidenote:  the iostream header file also has the conditional compilation directives in it because clearly, iostream is going to be used all over the place.  We'd get errors if it was constantly being redeclared.

The reason we did this is because now we can compile date.cpp and date.h independently.  By doing this, we have essentially set up our on mini-library for Dates.  The linker would be responsible for bringing the binary code into the picture (whereas before it was all done in one file and not linked in).

Ok, now how do we use the Date class?  It's easy, just tell your main() program you want to use dates by adding the appropriate #include, and you're ready to go!
 

// datetest.cpp

#include <iostream>
#include <string>
#include <conio>   // for getch()
#include "date.h"

int main() {
   Date today, finalDay;

   today.set(7,11,2001);
   finalDay.set(8,1,2001);

   cout << "today is ";
   today.print();
   cout << endl;

   cout << "the final is on ";
   finalDay.print();
   cout << endl;

   getch();   // hold the screen
}

figure 4

Think about what the #include "date.h" gives you:  it "plugs" the header file in there, thus letting the compiler know that Dates can be used in the program.  The code that does everything is not there, but it comes later when the libraries are linked in.  It's all pretty clever, really.  Now anyone who needs a Date object just needs to get their hands on date.h and date.cpp and they are ready to go!  In fact, they don't technically need date.cpp, but only the object file holding the compiled version of the methods.  This is how it is done in industry, frequently, because people don't want others messing with their source code.  This would be impossible without separate compilation.
 

Separate compilation in Borland

Here's how you set this all up in Borland. 
  1. Start a new project using all the usual settings (see lab0 for help), call the project datetest.
  2. Double click on the datetest.cpp node and copy and paste the code from figure 4 in the edit window that appears.
  3. Now right click on the datetest.exe node and choose "Add node". 
  4. type date.cpp and hit enter.
  5. Right click on date.cpp this time, choose "Add node", and do the same for date.h.  It should be a branch under the date.cpp node.  Your node tree should look something like this:
  6. Double click on date.h, then copy & paste the code from figure 2 into the text edit window.
  7. Do the same for date.cpp, but use the code from figure 3.


At this point, you should be able to click on the lightening bold and run your program.  If not, you may try modifying your include path as you had to do in lab 5 to include your project folder, but it should be enough to add it in as a node.

 

Last Updated: 7/8/01 by H. Chad Lane, hcl@cs.pitt.edu
© 2000-2001 Jim Skrentny, University of Wisconsin