4.4 Organizing the Code
The code that defines, implements and uses classes is organized as a collection of files. For very small programs it is possible to put all of the code in a single file. Clearly, though, this is a poor practice for anything but the most trivial systems. The organization that is described below works for all cases and is the pattern universally used in practice.
Separating the Interface Definition and the Implementation
The definitions of classes are placed in header files while code files contain the code that implements or uses the classes. By convention, the header files are named with a ".h" suffix. The conventional suffix for code files varies with the compiler or system; typical code file suffixes are" ".C", ".cc", or ".cpp". The base part of a file's name is, of course, chosen to reflect the nature of its contents. Which code file suffix is used is not important - what is important is using the same suffix consistently.
Each header file contains one class definition or several highly related class definitions. For example, the header file named "Frame.h" contains only the Frame class. In a windowing system there might also be separate header files giving the class definitions for buttons, sliders, pens, and other elements typically found in graphical user-interface systems. In many cases there are groups of class definitions that are closely related and are grouped together in one header file. Such grouping occurs in one of two cases. First, the definition of a window class may involve the use of other classes, in which case these other classes may also be defined in the same header file. Second, the designer of a set of classes may believe that most often a user will want an entire set of class definitions; putting them in one header file makes it easier for the user since the user need only examine one header file to find all of the related classes. For example, a graphics system might provide classes for rectangles, circles, colors, and rulers. A single header file, perhaps named "Graphics.h", could be used to hold all of these definitions. In organizing the header files, it is important to be sure that only highly related class definitions are put in the same file.
The code implementing a class is placed in its own code file; for example, the code implementing the Frame class might be placed in the file "Frame.cc". Even if several classes are defined in the same header file, their implementations are typically placed in separate code files.
Definition before Use
Because C++ is a statically typed language, the C++ compiler must see the definition of a class before any uses are made of that class. Three important cases in which this rule must be satisfied are:
Examples of these three cases are shown below.
To insure that the source code is presented to the compiler in a define-before-use order, the source code is first scanned by a simple preprocessesor. The preprocessor (often named "cpp" for the C/C++ Preprocessor Program) looks for simple commands or directives in the files that it is scanning. A directive begins with a pound sign (#) in the first column and the rest of the line contains information about the directive. These directives are, stricly speaking, not part of the C++ language, and are understood only by the preprocessor. The preprocessor may scan many files, but it alway produces exactly one output file. Each line of input that is not a directive is either copied unchanged to the output file or simply ignored and not copied to the output file. The preprocessor may also modify lines before they are output, but how or why this is done is not of concern here.
One of the most basic directives of the preprocessor allows new files to be included in the set of files being scanned. This directive has the syntax #include filename. When multiple files are included, the preprocessor scans the files with the aid of a stack data structure. For example suppose that the preprocessor is told to scan file A and that:
In this case, the preprocessor would produce as its single output file the text in this order:
A1, B1, E1, D1, B2, C1, E1, D1, A2
Notice that the processing order is stack-like. After scanning the text A1, the scanning of file A is suspended ("pushed on the stack"), while the scanning of the included files B and C is undertaken. Only after finishing the scanning of these included files is the scanning of file A resumed ("popped from the stack"). Also notice that files D and E appear twice in the output stream because file D was included twice, once from file B and once from file C.
Including Header Files
The preprocessor #include directive is used to insure that the compiler is presented with the source text in a define-before-use order. The example that follows shows how this directive should be used in header files and in the code files.
The Location class illustrates the first case in which a header file must be included. The interface and implementation of the Location class are shown again in the table below. Notice that the code file begins with an include directive.
The Message class is an example of the second case where include directives are needed. As shown below, the Message class interface refers to the Location and Frame classes because a Location object and Frame pointer appear as part of the private data of the class, and because the Message class methods take a Location object or a Frame reference as a parameter as well.
Notice that the code file (Message.cpp) includes the header file (Message.h) and that the header file of the Message class includes the header files of both the Location and Frame classes. It is not necessary for the code file to include the Location and Frame class header files because those have already been included by the Message class header file.
An example of the third case of using the include directive involves the FileChooser class that is shown in the table below. Notice that the interface of the FileChooser class uses only the single built-in type "char*" so there is no need to include any other files. The interface of the FileChooser does not depend on the interface of any other class. However, the implementation of the FileChooser does use two other classes, the Directory class and the Selector class. Objects of these two classes are used in the FileChooser's AskUser method. When compiling the code for the FileChooser class the compiler will need to verify that the Directory class defines methods named First() and Next() and that the Selector class defines methods named Add and AskUser. To satisfy the compiler's requirement, the implementation file, Choose.cpp, includes both Directry.h and Selector.h. It is important to note that the header file does not need to include either of these two files because the interface does not depend on the Directory and Selector classes, only the implementation depends on these two classes.
Care must be taken to avoid presenting the compiler with multiple definitions of the same class, even if these definitions are exactly the same. If the compiler sees two (even identical) definitions of the same class, it will issue an error message referring in some way to "multiple definitions" for the class. This situation can occur easily in practice when the preprocessor includes a header file twice in its output file. An example that follows will illustrate how easily it can happen. Additional preprocessor directives prevent this situation.
The BlinkingText program will be used to illustrate the problem of presenting the compiler with multiple definitions. The significant pieces of the files are shown in the table below. Notice that the use of the #include directives in each file is correct: for example, the BlinkingText.cpp file must include both the Frame.h and the Message.h header files because objects of both of these classes are declared. Also, the Location.h header file must be included by both the Frame.h file and the Message.h file because a Location object is a parameter to one or more of the methods in these files.
The problem of multiple definitions arises when the preprocessor scans the BlinkingText.cpp file; in this case the preprocessed output presented to the compiler will contain the following class definitions:
Location, Frame, Location, Message
The Location class appears twice because it is included by both the Frame.h file and the Message.h file.
Use of preprocessor variables will prevent duplicate class definitions. These variables help the preprocessor understand when an included file should be written to the preprocessor's output file or when the included file has already been written to the output file and should not be written again. A preprocessor variable is denoted by any string of characters, and for our purposes here, a preprocessor variable is either undefined (not currently known to the preprocessor) or defined (currently known to the preprocessor). Preprocessor variables that are defined may also have values attached to them, but how and why this is done is not relevant here. The preprocessor variables have nothing to do with the variables that are in the C++ code itself; they are simply names that are used during the preprocessing.
A preprocessor variable is defined by the preprocessor directive #define variablename. Any variable that has not been defined is undefined. Whether a variable is defined or not can be tested by another preprocessor directive, #ifndef variablename, which is true if the variable name is not defined and false otherwise, and a directive, #endif, that delimits the extent of the #ifndef. The preprocessor reacts to this directive in the following ways:
These three preprocessor directives (#define, #ifndef, and #endif) are used in a standard pattern to avoid the duplication of class definitions as shown for the Location.h file in the table below. Notice that the preprocessor variable has the rather strange name" _LOCATION_H". In standard practice the name of the preprocessor variable is some variation of the file name. This standard practice makes it easy to uniquely define each preprocessor variable, since there is only one file with a given name.
This usage causes the preprocessor to act in the following way:
After a little practice it becomes automatic to include these preprocessor directives in all header files.