Separate Compilation with GNU g++


This document explains how (and why) to compile a class as a separate module using GNU g++. The exact details depend on the compiler, but the general idea is the same for every compiler.

First of all, the only way to use a class in a program without separate compilation is to put into a single file the class definition, the method definitions and the program that uses the class, like this:


#include <iostream.h>
#include <string.h>   // for strcpy

// Type for representing a person's name with 49 characters or less.
class Name {
public:
   Name(const char n[ ] = "");        
    // Creates a name from n, using at most the first 50 
    // chars from n, up to and including the terminating '\0'.

   void copyToString(char target[ ]); 
    // Copies the characters of the name to target.
    // ASSUME: target array >= 50 elements.  

   void print( ) const;   // Prints the name to cout.
private:
   enum { maxName_ = 50 };
   char name_[maxName_];
};

Name :: Name(const char n[ ])
{
  int i;
  for (i = 0; i < maxName_ - 1 && n[i] != '\0'; i++) 
    name_[i] = n[i];
  name_[i] = '\0';
}

void Name :: copyToString(char target[ ])
{
   strcpy(target, name_);
}

void Name :: print( ) const
{
   cout << name_;
}

// A program for creating two Names 
// and deciding which is better.

int main( )
{
  char buffer[20];
  cout << "Enter your name: ";
  cin >> buffer;

  Name yourName(buffer), myName("Anonymous Bosch");

  myName.print( );
  cout << " is better than ";
  yourName.print( );
  cout << endl;

  return 0;
}

There are several disadvantages to this approach. First, once you have written and debugged the Name class, you must recompile it everytime you modify the program that uses Name objects. This takes a few extra seconds each time.

Second, compiling the Name class as a separate module makes sharing and reusing software easier. As it stands above, the only way to reuse the Name class is to cut out the program and insert another.

Third, you can use separate compilation to protect proprietary secrets. Suppose, for example, that you are selling the Name class, but you do not wish for anyone to see how the methods were written.

We can solve each of these problems by compiling the class as a separate module and linking that module to programs that use the class. We divide the class into a header file that contains the class definition and an implementation file that contains the method definitions. Here is the header file:


// File: Name.h
// Header file

#ifndef _NAMES_H  
#define _NAMES_H      

// Type for representing a person's name with 49 characters or less.
class Name {
public:
   Name(const char n[ ] = "");        
    // Creates a name from n, using at most the first 50 
    // chars from n, up to and including the terminating '\0'.

   void copyToString(char target[ ]); 
    // Copies the characters of the name to target.
    // ASSUME: target array >= 50 elements.  

   void print( ) const;   // Prints the name to cout.
private:
   enum { maxName_ = 50 };
   char name_[maxName_];
};

#endif

The #ifndef is called a macro guard and it prevents the file from being included more than once. If the symbol _NAMES_H has not already been defined, then it gets defined, starting with #define _NAMES_H and ending with #endif.

Now here is the implementation file, which contains the method definitions:


// File: Name.cpp
// Implementation file

#include <iostream.h>
#include <string.h>   // for strcpy
#include "Name.h"

Name :: Name(const char n[ ])
{
  int i;
  for (i = 0; i < maxName_ - 1 && n[i] != '\0'; i++) 
    name_[i] = n[i];
  name_[i] = '\0';
}

void Name :: copyToString(char target[])
{
   strcpy(target, name_);
}

void Name :: print( ) const
{
   cout << name_;
}

Now we can create a separate, compiled module for the Name class like this:

g++ -c Name.cpp
The result is a file Name.o in machine code. (The extension .o is for object file.) If you look at it with a text editor, it will appear as garbage. It is meaningful to the machine, but not to the human reader. You can now sell the Name class without revealing how the methods were written by shipping the header file and the object file. The client needs the header file to include in his program and to see the public interface, i.e., how client code interacts with Name objects, what are the Name behaviors. The linker needs the object file to put together the whole program:


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

// File: aProgram.cpp

// A program for creating two Names 
// and deciding which is better.

int main()
{
  char buffer[20];
  cout << "Enter your name: ";
  cin >> buffer;

  Name yourName(buffer), myName("Anonymous Bosch");

  myName.print();
  cout << " is better than ";
  yourName.print();
  cout << endl;
  return 0;
}

Notice the quotation marks in the second #include. This is telling the compiler that the header file is not in one of the standard directories, but rather is in the same directory as the current file. The client needs the header file to know how Name objects are used, and the compiler needs the header file to check that the client is using Name objects in the correct way, e.g., to check that the methods are taking/returning the correct types of arguments.

You can create an executable file like this:

g++ aProgram.cpp Name.o
Notice that this program can be edited and recompiled without having to recompile the Name class.


BU CAS CS - Separate Compilation with GNU g++
Web page by Drue Coles <dcoles@cs.bu.edu>