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( ): 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:
or
you can give it a value (which is the pre-Standard C way to define a constant):
In
either case, the label can now be tested by the preprocessor to see if it has
been defined: 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
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.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru