MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Header file etiquette

When you create a class, you are creating a new data type. Generally, you want this type to be easily accessible to yourself and others. In addition, you want to separate the interface (the class declaration) from the implementation (the definition of the class member functions) so the implementation can be changed without forcing a re-compile of the entire system. You achieve this end by putting the class declaration in a header file.

When I first learned to program in C, the header file was a mystery to me. Many C books don’t seem to emphasize it, and the compiler didn’t enforce function declarations, so it seemed optional most of the time, except when structures were declared. In C++ the use of header files becomes crystal clear. They are practically mandatory for easy program development, and you put very specific information in them: declarations. The header file tells the compiler what is available in your library. Because you can use the library without the source code for the CPP file (you only need the object file or library file), the header file is where the interface specification is stored.

The concept of a collection of associated functions combined into the same object module or library, and a header file containing all the declarations for the functions, is very standard when building large projects in C. It is de rigueur in C++: you could throw any function into a collection in C, but the class in C++ determines which functions are associated by dint of their common access to the private data. Any member function for a class must be declared in the class declaration; you cannot put it in some separate file. The use of function libraries was encouraged in C and institutionalized in C++.

Importance of using a common header file

When using a function from a library, C allows you the option of ignoring the header file and simply declaring the function by hand. You may want the compiler to speed up just a bit by avoiding the task of opening and including the file. For example, here’s an extremely lazy declaration of the C function printf( ):

printf(...);

It says: printf( ) has some number of arguments, and they all have some type but just take whatever arguments you see and accept them. By using this kind of declaration, you suspend all error checking on the arguments.

This practice can cause subtle problems. If you declare functions by hand in each different file, you may make a mistake the compiler accepts in a particular file. The program will link correctly, but the use of the function in that one file will be faulty. This is a tough error to find, and is easily avoided.

If you place all your function declarations in a header file, and include that file everywhere you use the function (and especially where you define the function) you insure a consistent declaration across the whole system. You also insure that the declaration and the definition match by including the header in the definition file.

C does not enforce this practice. It is very easy, for instance, to leave the header file out of the function definition file. Header files often confuse the novice programmer (who may ignore them or use them improperly).

If a class is declared in a header file in C++, you must include the header file everywhere a class is used and where class member functions are defined. The compiler will give an error message if you try to call a function without declaring it first. By enforcing the proper use of header files, the language ensures consistency in libraries, and reduces bugs by forcing the same interface to be used everywhere.

The header is a contract between you and the user of your library. It says, “Here’s what my library does.” It doesn’t say how because that’s stored in the .cpp file, and you won’t necessarily deliver the sources for “how” to the user. The user of the class simply includes the header file, creates objects (instances) of that class, and links in the object module or library (i.e.: the compiled code).

The contract describes your data structures, and states the arguments and return values for the function calls. The user needs all this information to develop the application and the compiler needs it to generate proper code.

The compiler enforces the contract by requiring you to declare all structures and functions before they are used and, in the case of member functions, before they are defined. Thus, you’re forced to put the declarations in the header and to include the header in the file where the member functions are defined and the file(s) where they are used. Because a single header file describing your library is included throughout the system, the compiler can ensure consistency and prevent errors.

There are certain issues that you must be aware of in order to organize your code properly and write effective header files. The first issue concerns what you can put into header files. The basic rule is “only declarations,” that is, only information to the compiler but nothing that allocates storage by generating code or creating variables. This is because the header file will probably be included in several translation units in a project, and if storage is allocated in more than one place, the linker will come up with a multiple definition error.

This rule isn’t completely hard and fast. If you define a variable that is “file static” (has visibility only within a file) inside a header file, there will be multiple instances of that data across the project, but the linker won’t have a collision. Basically, you don’t want to do anything in the header file that will cause an ambiguity at link time.

The preprocessor directives

#define, #ifdef and #endif

The preprocessor directive #define can be used to create create compile-time flags. You have two choices: you can simply tell the preprocessor that the flag is defined, without specifying a value:

#define FLAG

or you can give it a value (which is the pre-Standard C way to define a constant):

#define PI 3.14159

In either case, the label can now be tested by the preprocessor to see if it has been defined:

#ifdef FLAG

will yield a true result, and the code following the #ifdef will be included in the package sent to the compiler. This inclusion stops when the preprocessor encounters the statement

#endif

or

#endif // FLAG

Any non-comment after the #endif on the same line is illegal, even though some compilers may accept it. The #ifdef/#endif pairs may be nested within each other.

The complement of #define is #undef (short for “un-define”), which will make an #ifdef statement using the same variable yield a false result. #undef will also cause the preprocessor to stop using a macro. The complement of #ifdef is #ifndef, which will yield a true if the label has not been defined (this is the one we use in header files).

There are other useful features in the C preprocessor. You should check your local documentation for the full set.

Preventing re-declaration of classes

When you put a class declaration in a header file, it is possible for the file to be included more than once in a complicated program. The streams class is a good example. Any time a class does I/O (especially in inline functions) it may include the streams class. If the file you are working on uses more than one kind of class, you run the risk of including the streams header more than once and re-declaring streams.

The compiler considers the redeclaration of a class to be an error, since it would otherwise allow you to use the same name for different classes. To prevent this error when multiple header files are included, you need to build some intelligence into your header files using the preprocessor (the streams class already has this “intelligence”).

Both C and C++ allow you to redeclare a function, as long as the two declarations match, but neither will allow the redeclaration of a structure. In C++ this rule is especially important because if the compiler allowed you to redeclare a structure and the two declarations differed, which one would it use?

The problem of redeclaration comes up quite a bit in C++ because each data type (structure with functions) generally has its own header file, and you have to include one header in another if you want to create another data type that uses the first one. In the whole project, it’s very likely that you’ll include several files that include the same header file. During a single compilation, the compiler can see the same header file several times. Unless you do something about it, the compiler will see the redeclaration of your structure.

A standard for class header files

In each header file that contains a class, you should first check to see if the file has already been included in this particular code file. You do this by checking a preprocessor flag. If the flag isn’t set, the file wasn’t included and you should set the flag (so the class can’t get re-declared) and declare the class. If the flag was set the class has already been declared so you should just ignore the code declaring the class. Here’s how the header file should look:

#ifndef CLASS_FLAG
#define CLASS_FLAG
// Class declaration here...
#endif // CLASS_FLAG 

As you can see, the first time the header file is included, the class declaration will be included by the preprocessor but all the subsequent times the class declaration will be ignored. The name CLASS_FLAG can be any unique name, but a reliable standard to follow is to take the name of the header file and replace periods with underscores (leading underscores are reserved for system names). Here’s an example:

//: C04:Simple.h
// Simple class that prevents re-definition
#ifndef SIMPLE_H
#define SIMPLE_H

class Simple {
  int i,j,k;
public:
  Simple() { i = j = k = 0; }
};

#endif // SIMPLE_H ///:~ 

Although the SIMPLE_H after the #endif is commented out and thus ignored by the preprocessor, it is useful for documentation.

The preprocessor statements to prevent multiple inclusion are often referred to as include guards .

Namespaces in headers (??)

Using headers in projects

When building a project in C++, you’ll usually create it by bringing together a lot of different types (data structures with associated functions). You’ll usually put the declaration for each type or group of associated types in a separate header file, then define the functions for that type in a translation unit. When you use that type, you must include the header file to perform the declarations properly.

Sometimes that pattern will be followed in this book, but more often the examples will be very small, so everything – the structure declarations, function definitions, and the main( ) function – may appear in a single file. However, keep in mind that you’ll want to use separate files and header files in practice.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru