Separate Compilation: An Example

It is quite common to divide medium to large C++ programs into separately compiled modules. In these notes, we’ll see how to separately compile a class called RGB_color (for representing colors).

The General Idea

The command g++ -c somefile.cpp while compile the file somefile.cpp and produce an output file called somefile.o. The .o indicates that this is an object code file. In general, the input to a compiler is a source code file, and the output of the compiler is an object file.

You cannot run object files directly: they contained the results of compilation, but they are not executable. To create an executable program, you have to link the object files using a linker (g++ -o is the linker we’ll use).

So far we’ve had g++ combine compiling and linking into a single step for us. But for separate compilation, we will individually compile .cpp files, and then link the resulting .o object files.

To separately compile a class, you have to:

  • Move all the methods outside of the class so that the class only contains method headers and member variables.

  • Put the class into a .h header file. Usually the header file is the same name as the class, e.g. if your class is named RGB_color, you put it in RGB_color.h.

  • Put the implementations of all the methods into a .cpp. If the class is named RGB_color, then this file would normally be called RGB_color.cpp. Importantly, this .cpp file does not have a main function; main will appear in some other .cpp file that uses the class.

    To compile this class use this command:

    $ g++ -c RGB_color.cpp   // note the -c option
    

    This creates the file RGB_color.o.

  • To create an executable file, you need to create one more file, say color_test.cpp, that contains the main function. You compile it like this:

    $ g++ -c color_test.cpp   // note the -c option
    

    This creates the file color_test.o.

    Finally, to create the executable file we link the .o files like this:

    $ g++ -o color_test color_test.o RGB_color.o  // note the -o option
    

    g++ with the -o option calls the linker, and combines the two .o object files into a single executable file called color_test. Now you can run it like this:

    $ ./color_test
    

All In One File: RGB_color_allInOne.cpp

This file contains:

  • the RGB_color class with the implementations of all methods inside the class;
  • a couple of non-method functions (dist and operator<<) defined after the class
  • a main function that tests RGB_color

To run this code, type this command (it assumes you have the course makefile in the same folder):

$ make RGB_color_allInOne

This creates an executable file named RGB_color_allInOne that run like this:

$ ./RGB_color_allInOne
// RGB_color_allInOne.cpp

#include "cmpt_error.h"
#include <iostream>
#include <cmath>

using namespace std;

class RGB_color {
        int red = 0;
        int green = 0;
        int blue = 0;
public:
        RGB_color() // default constructor
        { }  // member initialization is used to set red, green, blue

        RGB_color(int r, int g, int b)
        : red(r), green(g), blue(b)
        {
                if (red < 0 || red > 255) cmpt::error("bad red value");
                if (green < 0 || green > 255) cmpt::error("bad green value");
                if (blue < 0 || blue > 255) cmpt::error("bad blue value");
        }

        RGB_color(const RGB_color& other)
        : RGB_color(other.red, other.green, other.blue)
        { }

        int get_red() const { return red; }
        int get_green() const { return green; }
        int get_blue() const { return blue; }

        void invert();
}; // class RGB_color

void RGB_color::invert() {
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
}

ostream& operator<<(ostream& out, const RGB_color& c) {
        out << "(" << c.get_red() << ", "
                   << c.get_green() << ", "
                   << c.get_blue() <<
               ")";
        return out;
}

// returns the distance between two colors
double dist(const RGB_color& a, const RGB_color& b) {
    cout << "dist(" << a << ", " << b << ") ...\n";
        int dr = a.get_red() - b.get_red();
        int dg = a.get_green() - b.get_green();
        int db = a.get_blue() - b.get_blue();
        return sqrt(dr*dr + dg*dg + db*db);
}

int main() {
        RGB_color bg(200, 100, 55);
        cout << "bg=" << bg << "\n";

        RGB_color fill(55, 155, 200);
        cout << "fill=" << fill << "\n";

        double d = dist(bg, fill);
        cout << "d=" << d << "\n";
}

Methods Outside the Class: RGB_color_split.cpp

This file contains:

  • the implementations of all the methods of the RGB_color class ware put outside the class; only the method headers (and member variables) remain in the class
  • the names of methods declared outside of the RGB_color class must start with RGB_color:: so that C++ knows they are part of the RGB_color class
  • a couple of non-method functions (dist and operator<<) defined after the class
  • a main function that tests RGB_color

To run this code, type this command (it assumes you have the course makefile in the same folder):

$ make RGB_color_split

This creates an executable file named RGB_color_allInOne that run like this:

$ ./RGB_color_split
// RGB_color_split.cpp

// This file shows how the implementations of the methods can be written
// outside of the RGB_color class.

#include "cmpt_error.h"
#include <iostream>
#include <cmath>

using namespace std;

class RGB_color {
        int red = 0;
        int green = 0;
        int blue = 0;
public:
        RGB_color();
        RGB_color(int r, int g, int b);
        RGB_color(const RGB_color& other);

        int get_red() const;
        int get_green() const;
        int get_blue() const;

        void invert();
}; // class RGB_color

//
// The following are methods defined inside the RGB_color class, and so they all
// start with RGB_color::
//

RGB_color::RGB_color() // default constructor
{ }  // member initialization is used to set red, green, blue

RGB_color::RGB_color(int r, int g, int b)
: red(r), green(g), blue(b)
{
        if (red < 0 || red > 255) cmpt::error("bad red value");
        if (green < 0 || green > 255) cmpt::error("bad green value");
        if (blue < 0 || blue > 255) cmpt::error("bad blue value");
}

RGB_color::RGB_color(const RGB_color& other)
: RGB_color(other.red, other.green, other.blue)
{ }

int RGB_color::get_red() const { return red; }
int RGB_color::get_green() const { return green; }
int RGB_color::get_blue() const { return blue; }

void RGB_color::invert() {
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
}

//
// The following are functions (not methods) defined outside RGB_color.
//

ostream& operator<<(ostream& out, const RGB_color& c) {
        out << "(" << c.get_red() << ", "
                   << c.get_green() << ", "
                   << c.get_blue() <<
               ")";
        return out;
}

// returns the distance between two colors
double dist(const RGB_color& a, const RGB_color& b) {
    cout << "dist(" << a << ", " << b << ") ...\n";
        int dr = a.get_red() - b.get_red();
        int dg = a.get_green() - b.get_green();
        int db = a.get_blue() - b.get_blue();
        return sqrt(dr*dr + dg*dg + db*db);
}

int main() {
        RGB_color bg(200, 100, 55);
        cout << "bg=" << bg << "\n";

        RGB_color fill(55, 155, 200);
        cout << "fill=" << fill << "\n";

        double d = dist(bg, fill);
        cout << "d=" << d << "\n";
}

A Header and an Implementation: RGB_color.h and RGB_color.cpp

The file RGB_color.h contains:

  • the RGB_color class containing just method headers (and member variables), along with just the headers of the two functions, dist and operator<<

  • the file begins and ends with pre-processor commands:

    #ifndef RGB_COLOR_H
    #define RGB_COLOR_H
    
    // ...
    
    
    #endif  // matches the #ifndef
    

    the C++ pre-processor runs before the compiler, and it is all of its commands begin with # (so #include is a pre-processor command)

  • #ifndef means “if the following symbol is not defined …”, and so #ifndef RGB_COLOR_H tests to see if the pre-processor symbol RGB_COLOR_H has been defined; the first time the #ifndef command runs, RGB_COLOR_H will not be defined, but every subsequent time it is run it will be defined (thanks to the #define RGB_COLOR_H command)

  • the purpose of using #ifndef and #include like this is to permit multiple inclusion of RGB_color.h; without this trick, if you happen to #include RGB_color.h more than once (something that is very easy to do in big programs) you’ll get a compiler error saying the class RGB_color has already been defined

  • RGB_color.h is not compile or linked directly: it is simply #included by any file that uses RGB_color

The file RGB_color.cpp contains:

  • the implementations of all of the RGB_color methods
  • the implementations of the functions dist and operator<<
  • importantly, RGB_color.cpp does not contain a main function

RGB_color.cpp is compiled with this command:

$ g++ -c RGB_color.cpp   // note the -c option

This creates a .o object file named RGB_color.o. Since there is no main function, we can’t create an executable file.

Linking to Create a Test Program: color_test.cpp

This file contains:

  • #include "RGB_color.h" at the top so that it can use the RGB_color class
  • a main function with some code for testing RGB_color

RGB_color.cpp is compiled with this command:

$ g++ -c color_test.cpp   // note the -c option

This creates a .o object file named color_test.o.

Now, since both color_test.o and RGB_color.o have been created, we can link them together to make the final executable file using this command:

$ g++ -o color_test color_test.o RGB_color.o  // note the -o option

This creates the executable file color_test which can be run like this:

$ ./color_test
// color_test.cpp

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

using namespace std;

int main() {
        RGB_color bg(200, 100, 255);
        cout << "bg=" << bg << "\n";

        RGB_color fill(55, 155, 200);
        cout << "fill=" << fill << "\n";

        double d = dist(bg, fill);
        cout << "d=" << d << "\n";
}

A Note about Makefiles

The following commands are needed to create the color_test executable file:

g++ -c RGB_color.cpp                        // compile
g++ -c color_test.cpp                       // compile
g++ -o color_test color_test.o RGB_color.o  // link

In addition, g++ is usually run with many other options which we have skipped here to make the commands more readable.

The make program, and makefiles, were designed specifically deal with issues of compiling and linking code like this. Makefiles essentially provide a special-purpose scripting language for building C/C++ (and other) programs. It is somewhat intelligent, and can avoid unnecessarily compiling programs if it knows that the source code file has not changed.

make is an older program and the syntax is a bit messy and unusual, so we will not go into any more details about it here. But keep in mind that big programs, or any program that uses separate compilation, needs to use make, or some tool like it to manage the commands and options for compiling and linking.