Separate Compilation

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:

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:

So the header file (Account.h) will have the form:

 1 
 2 
 3   #include<iostream>
 4   #include<string>
 5  
 6   using std::string; 1  // 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 

1

You should not place using directives in header files where possible. If we were to use using namespace std; in our header file, all cpp files that include this header would also include this using directive. This would have the effect of turning off namespaces in your project (in this case for std only).

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.”.

Figure 3.11. Compilation, and the output from the Separately Compiled Example.

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.