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
.hheader file. Usually the header file is the same name as the class, e.g. if your class is namedRGB_color, you put it inRGB_color.h.Put the implementations of all the methods into a
.cpp. If the class is namedRGB_color, then this file would normally be calledRGB_color.cpp. Importantly, this.cppfile does not have amainfunction;mainwill appear in some other.cppfile 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 themainfunction. 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
.ofiles like this:$ g++ -o color_test color_test.o RGB_color.o // note the -o option
g++with the-ooption calls the linker, and combines the two.oobject files into a single executable file calledcolor_test. Now you can run it like this:$ ./color_test
All In One File: RGB_color_allInOne.cpp¶
This file contains:
- the
RGB_colorclass with the implementations of all methods inside the class; - a couple of non-method functions (
distandoperator<<) defined after the class - a
mainfunction that testsRGB_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_colorclass 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_colorclass must start withRGB_color::so that C++ knows they are part of theRGB_colorclass - a couple of non-method functions (
distandoperator<<) defined after the class - a
mainfunction that testsRGB_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_colorclass containing just method headers (and member variables), along with just the headers of the two functions,distandoperator<<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#includeis a pre-processor command)#ifndefmeans “if the following symbol is not defined …”, and so#ifndef RGB_COLOR_Htests to see if the pre-processor symbolRGB_COLOR_Hhas been defined; the first time the#ifndefcommand runs,RGB_COLOR_Hwill not be defined, but every subsequent time it is run it will be defined (thanks to the#define RGB_COLOR_Hcommand)the purpose of using
#ifndefand#includelike this is to permit multiple inclusion ofRGB_color.h; without this trick, if you happen to #includeRGB_color.hmore than once (something that is very easy to do in big programs) you’ll get a compiler error saying the classRGB_colorhas already been definedRGB_color.his not compile or linked directly: it is simply #included by any file that usesRGB_color
The file RGB_color.cpp contains:
- the implementations of all of the
RGB_colormethods - the implementations of the functions
distandoperator<< - importantly,
RGB_color.cppdoes not contain amainfunction
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 theRGB_colorclass- a
mainfunction with some code for testingRGB_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.