C++ supports separate compilation, where pieces of the program can be compiled independently through the
two stage approach of compilation and then linking, so changes to one class would not necessarily require the
re-compilation of the other classes. The compiled pieces of code ( .o
or .obj
files)[8] are combined through the use of the linker
(in the use of Borland C++ it is ilink32.exe).
Separate compilation allows programs to be compiled and tested one class at a time, even built into libraries for
later use. It is therefore good practice to place each class in a separate source file to take full advantage of
separate compilation with the C++ language.
The source code for each class is stored in two files:
A source file (.cpp
) - the implementation of the methods.
A header file (.h
) - the definition of the class.
The header file contains the declarations for the methods contained in the cpp file, allowing for these cpp files to be compiled into libraries. The cpp file will define the methods and by including the header file within the cpp file you will ensure consistency between the declarations and definitions.
So the Account
class would take the form of three separate files:
Account.h
- That stores the class declaration and definition.
Account.cpp
- That stores the method definitions for that class.
Application.cpp
- That stores the application, i.e.
the main()
method for the application.
So the header file (Account.h
) will have the form:
1 2 3 #include<iostream> 4 #include<string> 5 6 using std::string;// only string is required 7 8 class Account{ 9 10 protected: 11 12 int accountNumber; 13 float balance; 14 string owner; 15 16 public: 17 18 Account(string owner, float aBalance, int anAccountNumber); 19 Account(float aBalance, int anAccountNumber); 20 Account(int anAccountNumber); 21 Account(const Account &sourceAccount); 22 23 ... 24 }; 25
The implementation file (Account.cpp
) will have the form:
#include "Account.h"
using namespace std;
Account::Account(string anOwner, float aBalance, int anAccNumber):
accountNumber(anAccNumber), balance(aBalance),
owner (anOwner) {}
Account::Account(float aBalance, int anAccNumber) :
accountNumber(anAccNumber), balance(aBalance),
owner ("Not Defined") {}
Account::Account(int anAccNumber):
accountNumber(anAccNumber), balance(0.0f),
owner ("Not Defined") {}
Account::Account(const Account &sourceAccount):
accountNumber(sourceAccount.accountNumber + 1),
balance(0.0f),
owner (sourceAccount.owner) {}
...
And the application (Application.cpp
) will have the form:
#include "Account.h"
int main()
{
Account a = Account("Derek Molloy",35.00,34234324);
...
}
To compile the application, you must now specify the files to be used in the compilation. So, to compile
all the files at once use: bcc32 Application.cpp Account.cpp, where one of the source
files contains a main()
method. This can be seen in Figure 3.11, “Compilation, and the output from the Separately Compiled Example.”.
Just before we continue we need to briefly discuss preprocessor directives. Preprocessor directives are orders for the preprocessor, not for the program itself. They must be specified in a single line of code and should not end with a ; (semicolon). Some preprocessor directives are: #include (insert a header file here), #define (define a constant macro)
#define PI 3.14
#undef (removes definition), #if, #ifdef, #ifndef, #endif, #else, #elif (control directives to remove part of a program depending on the condition),
#ifndef MAX_WIDTH #define MAX_WIDTH 1000 #endif
#line (allows control over compile time error messages), #error (allows us to abort compilation if required), e.g.
#ifndef __cplusplus #error You need a C++ compiler for this code! #endif
and #pragma (used for compiler options specific to a particular platform and compiler).
If there are multiple classes, some of which use the same parent, you can use compiler directives to prevent the re-definition of the same class, which would result in a compiler error. These directives can be placed around the class definition such as:
#ifndef currentAccount_h //check not already defined #define currentAccount_h //if not, define! #include "Account.h" //include the account header class CurrentAccount: public Account{ protected: float overDraftLimit; ... public: CurrentAccount(int theNumber, char* theOwner, float theOverdraftLimit); ... }; #endif // currentAcount_h
In this case, the compiler directives simply state that if the CurrentAccount
class is
already defined then do not redefine it. This is determined by the currentAccount_h
value,
that if undefined is simply defined, and so used as a flag. This process is to ensure that we have not broken the
C++ single definition rule: You can
declare anything as many times as you want, but you can only define it once.
Here is an example of the complexities that arise with separate compilation. In this example A
is the parent of AA
and B
. AA
is a child of
A
and B
is a part-of AA
. B
is a child of A
and AA
is a part-of B
. I have added
the minimum namespace usage necessary in the header files:
testapp.cpp
A.h
A.cpp
AA.h
AA.cpp
B.h
B.cpp
Fortunately, for the purpose of professional development, tools such as Integrated Development Environments(IDEs) can automatically insert the required preprocessor directives.
[8] The .o
or .obj
filename extensions are called object
files, but importantly this has nothing to do with object, as in object-oriented, rather it simply means object, as
in goal or aim.
© 2006
Dr. Derek Molloy
(DCU).