Home > Articles > Programming > C/C++

Separate Compilation and Namespaces in C++

📄 Contents

  1. Separate Compilation
  2. Namespaces
  3. Summary
  • Print
  • + Share This
  • 💬 Discuss

This chapter is from the book

Organizing a C++ program into separate parts (by using separate compilation and namespaces) is covered in this article by expert Walter Savitch.
This article is derived from Absolute C++ (Addison-Wesley, 2002), by Walter Savitch.
From mine own library with volumes that I prize above my dukedom.
—William Shakespeare, The Tempest

This article covers two topics that have to do with how you organize a C++ program into separate parts. In the first part, "Separate Compilation," we discuss how a C++ program can be distributed across a number of files so that when some parts of the program change, only those parts need to be recompiled, and the separate parts can be more easily reused in other applications.

In the second section, we discuss namespaces, which are a way of allowing you to reuse the names of classes, functions, and other items by qualifying the names to indicate different uses of the names. Namespaces divide your code into sections, so that the different sections may reuse the same names with differing meanings. It is a kind of local meaning for names that is more general than local variables.

Separate Compilation

Your "if" is the only peacemaker; much virtue in "if."
—William Shakespeare, As You Like It

C++ has facilities for dividing a program into parts that are kept in separate files, compiled separately, and then linked together when (or just before) the program is run. You can place the definition for a class (and its associated function definitions) in files that are separate from the programs that use the class. That way, you can build up a library of classes so that many programs can use the same class. You can compile the class once and then use it in many different programs, just like you use the predefined libraries such as those with header files iostream and cstdlib. Moreover, you can define the class itself in two files so that the specification of what the class does is separate from how the class is implemented. If you only change the implementation of the class, then you need only recompile the file with the class implementation. The other files, including the files with the programs that use the class, need not be changed or even recompiled. In this section, we tell you how to carry out this separate compilation of classes.

Encapsulation Reviewed

The principle of encapsulation says that you should separate the specification of how the class is used by a programmer from the details of how the class is implemented. The separation should be so complete that you can change the implementation and any program that uses the class should not need to be changed at all. The way to ensure this separation can be summarized in three rules:

  • Make all the member variables private members of the class.

  • Make each of the basic operations for the class either a public member function of the class, a friend function, an ordinary function, or an overloaded operator. Group the class definition and the function and operator declarations (prototypes) together. This group, along with its accompanying comments, is called the interface for the class. Fully specify how to use each such function or operator in a comment given with the class or with the function or operator declaration.

  • Make the implementation of the basic operations unavailable to the programmer who uses the class. The implementation consists of the function definitions and overloaded operator definitions (along with any helping functions or other additional items these definitions require).

In C++, the best way to ensure that you follow these rules is to place the interface and the implementation of the class in separate files. As you might guess, the file that contains the interface is often called the interface file, and the file that contains the implementation is called the implementation file. The exact details of how to set up, compile, and use these files will vary slightly from one version of C++ to another, but the basic scheme is the same in all versions of C++. In particular, the details of what goes into the files are the same in all systems. The only things that vary are what commands you use to compile and link these files. The details about what goes into these files are illustrated in the next subsection.

A typical class has private member variables. Private member variables (and private member functions) present a problem to our basic philosophy of placing the interface and the implementation of a class in separate files. The public part of the class definition for a class is part of the interface for the class, but the private part is part of the implementation. This is a problem because C++ will not allow you to split the class definition across two files. Thus, some sort of compromise is needed. The only sensible compromise is to place the entire class definition in the interface file. Since a programmer who is using the class cannot use any of the private members of the class, the private members will, in effect, still be hidden from the programmer.

Header Files and Implementation Files

Display 1 contains the interface file for a class called DigitalTime.

Display 1—Interface File for the DigitalTime class

//This is the header file dtime.h. This is the interface for the class DigitalTime.
//Values of this type are times of day. The values are input and output in 24 hour
//notation as in 9:30 for 9:30 AM and 14:45 for 2:45 PM.
#include <iostream>
using namespace std;

class DigitalTime
{
public:
  DigitalTime(int theHour, int theMinute);
  DigitalTime( );
  //Initializes the time value to 0:00 (which is midnight).

  getHour( ) const;
  getMinute( ) const;
  void advance(int minutesAdded);
  //Changes the time to minutesAdded minutes later.

  void advance(int hoursAdded, int minutesAdded);
  //Changes the time to hoursAdded hours plus minutesAdded minutes later.

  friend bool operator ==(const DigitalTime& time1, 
              const DigitalTime& time2);

  friend istream& operator >>(istream& ins, DigitalTime& theObject);

  friend ostream& operator <<(ostream& outs, const DigitalTime& theObject);

private:
 
  int hour;
  int minute;
 
  static void readHour(int& theHour);
  //Precondition: Next input in to be read from the keyboard is 
  //a time in notation, like 9:45 or 14:45.
  //Postcondition: theHour has been set to the hour part of the time. 
  //The colon has been discarded and the next input to be read is the minute.

Display 1 	Interface File for the DigitalTime class (part 2 of 2)

  static void readMinute(int& theMinute);
  //Reads the minute from the keyboard after readHour has read the hour.

  static int digitToInt(char c);
  //Precondition: c is one of the digits '0' through '9'.
  //Returns the integer for the digit; for example, digitToInt('3') returns 3.

};

The private members are part of the implementation, even though they are in the interface file. The label private: warns you that these private members are not part of the public interface. Everything that a programmer needs to know in order to use the class DigitalTime is explained in the comment at the start of the file and in the comments in the public section of the class definition. As noted in the comment at the top of the interface file, this class uses 24-hour notation, so, for instance, 1:30 PM is input and output as 13:30. This and the other details you must know in order to effectively use the class DigitalTime are included in the comments given with the member functions.

We have placed the interface in a file named dtime.h. The suffix .h indicates that this is a header file. An interface file is always a header file and so always ends with the suffix .h. Any program that uses the class DigitalTime must contain an include directive like the following, which names this file:

#include "dtime.h"

When you write an include directive, you must indicate whether the header file is a predefined header file that is provided for you, or is a header file that you wrote. If the header file is predefined, you write the header file name in angular brackets, like <iostream>. If the header file is one that you wrote, then you write the header file name in quotes, like "dtime.h". This distinction tells the compiler where to look for the header file. If the header file name is in angular brackets, the compiler looks wherever the predefined header files are kept in your implementation of C++. If the header file name is in quotes, the compiler looks in the current directory or wherever programmer-defined header files are kept on your system.

Any program that uses our DigitalTime class must contain the above include directive that names the header file dtime.h. That is enough to allow you to compile the program, but is not enough to allow you to run the program. In order to run the program you must write (and compile) the definitions of the member functions and the overloaded operators. We have placed these function and operator definitions in another file, which is called the implementation file. Although it is not required by most compilers, it is tradition to give the interface file and the implementation file the same name. The two files do, however, end in different suffixes. We have placed the interface for our class in the file named dtime.h and the implementation for our class in a file named dtime.cpp. The suffix you use on the implementation file depends on your version of C++. Use the same suffix on the implementation file as you normally use on files that contain C++ programs. (Other common suffixes are .cxx, and .hxx.) The implementation file for our DigitalTime class is given in Display 2.

Display 2—Implementation File

//This is the implementation file: dtime.cpp of the class DigitalTime.
//The interface for the class DigitalTime is in the header file dtime.h.
#include <iostream>
#include <cctype>
#include <cstdlib>
using namespace std;
#include "dtime.h"

//Uses iostream and cstdlib:
DigitalTime::DigitalTime(int theHour, int theMinute)
{
  if (theHour < 0 || theHour > 24 || theMinute < 0 || theMinute > 59)
  {
    cout << "Illegal argument to DigitalTime constructor.";
    exit(1);
  }
  else
  {
    hour = theHour;
    minute = theMinute;
  }

  if (hour == 24)
    hour = 0; //standardize midnight as 0:00
}

DigitalTime::DigitalTime( )
{
  hour = 0;
  minute = 0;
}

int DigitalTime::getHour( ) const
{
  return hour;
}

int DigitalTime::getMinute( ) const
{
  return minute;
}

void DigitalTime::advance(int minutesAdded)
{
  int grossMinutes = minute + minutesAdded;
  minute = grossMinutes%60;

  int hourAdjustment = grossMinutes/60;
  hour = (hour + hourAdjustment)%24;
}

void DigitalTime::advance(int hoursAdded, int minutesAdded)
{
  hour = (hour + hoursAdded)%24;
  advance(minutesAdded);
}

bool operator ==(const DigitalTime& time1, const DigitalTime& time2)
{
  return (time1.hour == time2.hour && time1.minute == time2.minute);
}

//Uses iostream:
ostream& operator <<(ostream& outs, const DigitalTime& theObject)
{
  outs << theObject.hour << ':';
  if (theObject.minute < 10)
    outs << '0';
  outs << theObject.minute;
  return outs;
}

Any file that uses the class DigitalTime must contain the include directive:

#include "dtime.h"

This means that both the implementation file and the program file must contain this include directive that names the interface file. The file that contains the program (that is, the file that contains the main function) is often called the application file or driver file. Display 3 contains an application file with a very simple program that uses and demonstrates the DigitalTime class.

Display 3—Application File Using DigitalTime Class

//This is the application file: timedemo.cpp which demonstrates use of DigitalTime.
#include <iostream>
using namespace std;
#include "dtime.h"

int main( )
{
  DigitalTime clock, oldClock;

  cout << "You may write midnight as either 0:00 or 24:00,\n"
     << "but, I will always write it as 0:00.\n"
     << "Enter the time in 24 hour notation: ";
  cin >> clock;

  oldClock = clock;
  clock.advance(15);
  if (clock == oldClock)
    cout << "Something is wrong.";
  cout << "You entered " << oldClock << endl;
  cout << "15 minutes later the time will be "
     << clock << endl;

  clock.advance(2, 15);
  cout << "2 hours and 15 minutes after that\n"
     << "the time will be "
     << clock << endl;

  return 0;
}
Sample Screen Dialogue
You may write midnight as either 0:00 or 24:00,
but, I will always write it as 0:00.
Enter the time in 24 hour notation: 11:15
You entered 11:15
15 minutes later the time will be 11:30
2 hours and 15 minutes after that
the time will be 13:45

Compiling your program automatically invokes a preprocessor that reads this include directive and replaces this include directive with the text in the file dtime.h. Thus, the compiler sees the contents of dtime.h, and so the file dtime.h does not need to be compiled separately. (In fact, the compiler sees the contents of dtime.h twice: once when you compile the implementation file and once when you compile the application file.) This copying of the file dtime.h is only a conceptual copying. The compiler acts as if the contents of dtime.h were copied into each file that has the include directive. However, if you look in that file after it is compiled, you will only find the include directive; you will not find the contents of the file dtime.h.

Once the implementation file and the application file are compiled, you still need to connect these files so that they can work together. This is called linking the files, and is done by a separate utility called a linker. The details for how you call the linker depends on what system you are using. Often, the command to run a program automatically invokes the linker, so you need not explicitly call the linker at all. After the files are linked, you can run your program.

This sounds like a complicated process, but many systems have facilities that manage much of this detail for you automatically or semiautomatically. On any system, the details quickly become routine. On Unix systems, these details are handled by the make facility. In most IDEs (Integrated Development Environments) these various files are combined into something called a project.

Displays 1, 2, and 3 contain one complete program divided into pieces and placed in three different files. You could instead combine the contents of these three files into one file, and then compile and run this one file without all this fuss about include directives and linking separate files. Why bother with three separate files? There are several advantages to dividing your program into separate files. Since you have the definition and the implementation of the class DigitalTime in files separate from the application file, you can use this class in many different programs without needing to rewrite the definition of the class in each of the programs. Moreover, you need to compile the implementation file only once, no matter how many programs use the class DigitalTime. But there are more advantages than that. Since you have separated the interface from the implementation of your DigitalTime class, you can change the implementation file and will not need to change any program that uses the class. In fact, you will not even need to recompile the program. If you change the implementation file, you only need to recompile the implementation file and to relink the files. Saving a bit of recompiling time is nice, but the big advantage is not having to rewrite code. You can use the class in many programs without writing the class code into each program. You can change the implementation of the class and you need not rewrite any part of any program that uses the class.

The details of the implementation of the class DigitalTime are discussed in the next subsection.

Programming Example: DigitalTime Class

In the previous section, we described how the files in Displays 1, 2, and 3 divide a program into three files, the interface for the class DigitalTime, the implementation of the class DigitalTime, and an application that uses the class. In this section, we discuss the details of the class implementation. There is no new material in this section, but if some of the details in the implementation (Display 2) are not completely clear to you, this section may shed some light on your confusion.

Most of the implementation details are straightforward, but there are two things that merit comment. Notice that the member function name advance is overloaded so that it has two function definitions. Also notice that the definition for the overloaded extraction (input) operator >> uses two "helping functions" called readHour and readMinute, and these two helping functions themselves use a third helping function called digitToInt. Let's discuss these points.

The class DigitalTime (Displays 1, 2) has two member functions called advance. One version takes a single argument; that is, an integer giving the number of minutes to advance the time. The other version takes two arguments, one for a number of hours and one for a number of minutes, and advances the time by that number of hours plus that number of minutes. Notice that the definition of the two-argument version of advance includes a call to the one-argument version of advance. Look at the definition of the two-argument version that is given in Display 2. First, the time is advanced by hoursAdded hours and then the single argument version of advance is used to advance the time by an additional minutesAdded minutes. At first, this may seem strange, but it is perfectly legal. The two functions named advance are two different functions that, as far as the compiler is concerned, just coincidentally happen to have the same name.

Now let's discuss the helping functions. The helping functions readHour and readMinute read the input one character at a time and then convert the input to integer values that are placed in the member variables hour and minute. The functions readHour and readMinute read the hour and minute one digit at a time, so they are reading values of type char. This is more complicated than reading the input as int values, but it allows us to perform error-checking to see whether the input is correctly formed and to issue an error message if the input is not well-formed. These helping functions readHour and readMinute use another helping function named digitToInt. The function digitToInt converts a digit, such as '3', to a number, such as 3.

Programming Tip: Reusable Components

A class developed and coded into separate files is a software component that can be used again and again in a number of different programs. A reusable component saves effort because it does not need to be redesigned, recoded, and retested for every application. A reusable component is also likely to be more reliable than a component that is used only once. It is likely to be more reliable for two reasons. First, you can afford to spend more time and effort on a component if it will be used many times. Second, if the component is used again and again, it is tested again and again. Every use of a software component is a test of that component. Using a software component many times in a variety of contexts is one of the best ways to discover any remaining bugs in the software.

Using #ifndef

We have given you a method for placing a program in three (or more) files: two for the interface and implementation of each class and one for the application part of the program. A program can be kept in more than three files. For example, a program might use several classes and each class might be kept in a separate pair of files. Suppose you have a program spread across a number of files and more than one file has an include directive for a class interface file such as the following:

#include "dtime.h"

Under these circumstances, you can have files that include other files, and these other files may in turn include yet other files. This can easily lead to a situation in which a file, in effect, contains the definitions in dtime.h more than once. C++ does not allow you to define a class more than once, even if the repeated definitions are identical. Moreover, if you are using the same header file in many different projects, it becomes close to impossible to keep track of whether or not you included the class definition more than once. To avoid this problem, C++ provides a way of marking a section of code to say "if you have already included this stuff once before, do not include it again." The way this is done is quite intuitive, although the notation may look a bit weird until you get used to it. We will go through an example, explaining the details as we go.

The following directive defines DTIME_H:

#define DTIME_H

What this means is that the compiler's preprocessor puts DTIME_H on a list to indicate that DTIME_H has been seen. Defined is perhaps not the best word for this, since DTIME_H is not defined to mean anything but merely put on a list. The important point is that you can use another directive to test whether or not DTIME_H has been defined, and so test whether or not a section of code has already been processed. You can use any (nonkeyword) identifier in place of DTIME_H, but you will see that there are standard conventions for which identifier you should use.

The following directive tests to see whether or not DTIME_H has been defined:

#ifndef DTIME_H

If DTIME_H has already been defined, then everything between this directive and the first occurrence of the following directive is skipped:

#endif

(An equivalent way to state this, which may clarify the way the directives are spelled, is the following: If DTIME_H is not defined, then the compiler processes everything up to the next #endif. That is not why there is an n in #ifndef. This may lead you to wonder whether there is a #ifdef directive as well as a #ifndef directive. There is, and it has the obvious meaning, but we will have no occasion to use #ifdef.)

Now consider the code

#ifndef DTIME_H
 #define DTIME_H
 <a class definition>
#endif

If this code is in a file named dtime.h, then no matter how many times your program contains

#include "dtime.h"

the class will be defined only one time.

The first time

#include "dtime.h"

is processed, the flag DTIME_H is defined and the class is defined. Now, suppose the compiler again encounters

#include "dtime.h"

When the include directive is processed this second time, the directive

#ifndef DTIME_H

says to skip everything up to

#endif

and so the class is not defined again.

In Display 4, we have rewritten the header file dtime.h shown in Display 1.

Display 4—Avoiding Multiple Definitions of a Class

//This is the header file dtime.h. This is the interface for the class DigitalTime.
//Values of this type are times of day. The values are input and output in
//24 hour notation as in 9:30 for 9:30 AM and 14:45 for 2:45 PM.

#ifndef DTIME_H
#define DTIME_H

#include <iostream>
using namespace std;

class DigitalTime
{

    <The definition of the class DigitalTime is the same as in Display 1.>

};

#endif //DTIME_H

You may use some other identifier in place of DTIME_H, but the normal convention is to use the name of the file written in all uppercase letters with the underscore used in place of the period. You should follow this convention so that others can more easily read your code and so that you do not have to remember the flag name. This way, the flag name is determined automatically and there is nothing arbitrary to remember.

These same directives can be used to skip over code in files other than header files, but we will not have occasion to use these directives except in header files.

Programming Tip: Defining Other Libraries

You need not define a class in order to use separate compilation. If you have a collection of related functions that you want to make into a library of your own design, you can place the function declarations (prototypes) and accompanying comments in a header file and the function definitions in an implementation file, just as we outlined for classes. After that, you can use this library in your programs the same way you would use a class that you placed in separate files.

  • + Share This
  • 🔖 Save To Your Account
Absolute C++

This chapter is from the book

Absolute C++

Discussions

comments powered by Disqus

Related Resources

There are currently no related titles. Please check back later.