Part A - Introduction
Modular Programming
Partition the source code for a programming solution into modules
Compile the set of modules on Linux and Windows platforms
"Decide which modules you want; partition the
program so that data is hidden within modules" (Stroustrup, 1997)
Modules
| Stages of Compilation
| Example
| Unit Tests
| Summary
| Exercises
We store object-oriented source code in modules.
The technique of modularization predates object-oriented languages and is
found in languages like C. C lets us access much functionality outside
the core language through library modules. For instance, its
stdio module provides input and output support,
while hiding the implementation of scanf() and
printf() in a separate file. In object-oriented
solutions we store class definitions and the implementation of their logic in
modules.
This chapter describes how to create a set of modules, compile the source
code in each one, and link the compiled code into an executable binary.
This sets the stage for storing related classes in their own module.
The chapter concludes with an example of unit tests on a module.
Modules
A well-designed module is a highly cohesive unit that is loosely
coupled to other modules. It handles one aspect of a solution and
hides as much detail as possible. In an object-oriented language
like C++, a module contains the unit of source code that the compiler
translates independently into a unit of binary code.
A modular design for the retail store order application described in the
first chapter is illustrated below. The main module accesses the
Order module and the iostream module. The Order module contains
the Order and Special Order classes. The Order module accesses
the EAN module. The EAN module contains the GS1 Prefix and EAN
classes. The iostream module contains the classes that describe
the standard input and output objects.

In translating any module the compiler only needs enough information to
identify the names that are defined in any other modules. For this,
we store the source code for each module in two separate files:
- the header file - declares to the client the class definitions and
the function prototypes
- the implementation file - defines the functions that hold the logic
The extension .h (or .hpp)
identifies the header file. The extension .cpp
identifies the implementation files.
Note that the names of the header files for the standard C++ libraries
do not include a file extension. The <iostream>
header file contains the class definitions for cout and
cin. To inform the compiler that these object
names are valid, we include the header file for the
iostream module.
Example
There are three modules in the design illustrated above. The implementation
file for the main module includes the header files
for the itself and the Order and iostream
modules, but not their implementation files. The header file for the
Order module includes the header file for the
EAN module, but not the implementation file.
In other words, a module's header or implementation file only ever includes
the header files of other modules.

The implementation file of each module compiles separately and only once.
That is, we compile .cpp files, but not the header files.
We do not need to compile iostream's implementation file
since its compiled version is in the system library.

Stages of Compilation
Complete compilation consists of three independent and consecutive stages
(as shown in the figure below):
- Pre-processing - inserts the contents of the header files
into the implementation files and substitutes all
#define macros to create a single
translation unit
- Compiling - compiles each translation unit separately and
creates an independent binary for that unit
- Linkage - assembles the various binaries for the translation units
along with the system binaries to create the executable binary

A Trivial Example
As an example of modular design and compilation, consider a trivial
accounting application that accepts journal transaction data from the
standard input device and displays that data on the standard output
device without any intermediate modification. We shall refine
the source code for this application in our exercises as we proceed
through the next few chapters.
Our design consists of two modules:
- Main - supervises the input and output for all transactions
- Transaction - defines the logic for inputting
data and outputting data for one single transaction
Transaction Module
Let us start with a C-style structure that holds the information for a
single transaction and two global functions
- enter() - accepts transaction
data from the standard input device
- display() - displays
transaction data on the standard output device
Transaction.h
The header file defines our Transaction
structure and declares the two function prototypes:
// Modular Example
// Transaction.h
struct Transaction {
int acct; // account number
char type; // credit 'c' debit 'd'
double amount; // transaction amount
};
void enter(struct Transaction* tr);
void display(const struct Transaction* tr);
|
Note the UML naming convention and the extension on the file's name.
Transaction.cpp
The implementation file defines the two functions.
It includes the system header file that defines the cout
and cin objects and the header file that defines the
Transaction structure. The implementation file has
a .cpp extension:
// Modular Example
// Transaction.cpp
#include <iostream>
using namespace std;
#include "Transaction.h"
// prompts for and accepts Transaction data
//
void enter(struct Transaction* tr) {
cout << "Enter the account number : ";
cin >> tr->acct;
cout << "Enter the account type (d debit, c credit) : ";
cin >> tr->type;
cout << "Enter the account amount : ";
cin >> tr->amount;
}
// displays Transaction data
//
void display(const struct Transaction* tr) {
cout << "Account " << tr->acct;
cout << ((tr->type == 'd') ? " Debit $" : " Credit $") << tr->amount;
cout << endl;
}
|
Main Module
The main module defines one Transaction object
and calls the global functions defined in our Transaction module.
main.h
The main module's header file #defines the number of transactions to be
processed:
// Modular Example
// main.h
#define NO_TRANSACTIONS 3
|
main.cpp
The main module's implementation file
defines the main()
function. We #include the
header file for the Transaction module to inform the compiler
that the Transaction structure is a
valid structure and that the function calls are valid calls:
// Modular Example
// main.cpp
#include "main.h"
#include "Transaction.h"
int main() {
int i;
struct Transaction tr;
for (i = 0; i < NO_TRANSACTIONS; i++) {
enter(&tr);
display(&tr);
}
}
|
Command Line Compilation
Linux
Our Linux platform hosts the GNU g++ compiler. To compile
our application on it, we enter the command
g++ -o accounting main.cpp Transaction.cpp
|
The -o option identifies the name
of the executable binary. The names of the two implementation
files follow this option.
To run the executable binary, we enter
Visual Studio
Our Windows platform hosts the Visual Studio compiler.
To compile our application at the command-line, we enter the
command
cl -oaccounting main.cpp Transaction.cpp
|
You can access this compiler through the Visual Studio
command prompt window. To open the window, press
Start > All Programs and
search for the prompt in the Visual Studio Tools
sub-directory.
To run the executable, we enter
Unit Tests
Modular programs are well suited to unit testing. A unit test
is a code snippet that tests a single assumption in a work unit of a complete
program. A work unit is a single logical component with a
simple interface. A typical work unit is a function. A
suite of unit tests examines a program's work units and can be rerun
after each upgrade. We store the test suite in a separate module.
For example, consider a Calculator module, which includes
the capability to raise an integer to the power of an integer exponent
and to determine the integer exponent to which an integer base has been raised to
obtain a given result. The header file for the Calculator
module includes the prototypes for these two work units:
// Calculator.h
// ...
int power(int, int);
int exponent(int, int);
|
The suite of unit tests checks if the implementations return the
expected results. The header file for the Tester
module contains:
// Tester.h
int testSuite(int BASE, int EXPONENT, int RESULT);
|
The implementation file for the Tester
module contains:
// Tester.cpp
#include<iostream>
using namespace std;
#include "Calculator.h"
int testSuite(int BASE, int EXPONENT, int RESULT) {
int passed = 0;
int result;
result = power(BASE, EXPONENT);
if (result == RESULT) {
cout << "Raise to Power Test Passed" << endl;
passed++;
}
else {
cout << "Raise to Power Test Failed" << endl;
}
result = exponent(RESULT, BASE);
if (result == EXPONENT) {
cout << "Find Exponent Test Passed" << endl;
passed++;
}
else {
cout << "Find Exponent Test Failed" << endl;
}
return passed;
}
|
A first attempt at implementing the Calculator
module look like:
// Calculator.cpp
#include "Calculator.h"
int power(int base, int exp) {
int i, result = 1;
for (i = 0; i < exp; i++)
result *= base;
return result;
}
int exponent(int result, int base) {
int exp = 0;
while(result >= base) {
exp++;
result /= base;
}
return exp;
}
|
In this case, the following test main produces the results shown on the right:
// Test Main
// testmain.cpp
#include<iostream>
using namespace std;
#include "Tester.h"
int main() {
int passed = 0;
passed += testSuite(5, 3, 125);
passed += testSuite(5, -3, 125);
cout << passed << " Tests Passed" << endl;
}
|
Raise to Power Test Passed
Find Exponent Test Passed
Raise to Power Test Failed
Find Exponent Test Failed
2 Tests Passed
|
Clearly, the implementation needs to be upgraded to handle bases that
are negative.
Recommended Approach
One recommended approach to coding any implementation is to write the suite of unit tests for
the work units in a module as soon as we have defined its header file
and before starting to code the bodies of the work units in the implementation file.
As we fill in the implementation details, we can continue testing the module
for the results that we expect.
Summary
- a module consists of a header file and an implementation
file
- a module's header file declares the names that
other modules may reference
- a module's implementation file defines
the module's logic
- we only include a module's header files in the source code for
other modules
- three stages involved in creating an executable binary are
pre-processing, compiling, and linking
- the suite of unit tests for a module should be written before
the implementation is complete
Exercises
|