blob: c6fa6d38b54f6c48fc45666d7c76f10a4d41f52c [file] [log] [blame]
Header Policy
-------------
The C++ Standard specifies many mutual dependencies among the
headers it defines. It offers no advice on how to arrange headers
to avoid problems. The worst such problem is circular references.
Most simply this is "A includes B, B includes A":
// file <A> // file <B>
#ifndef A #ifndef B
#define A 1 #define B 1
#include <B> #include <A>
typedef int A_type; typedef int B_type;
extern B_type g(A_type); extern A_type f(B_type);
#endif /* A */ #endif /* B */
// file C.cc
#include <A>
The typical effect of such an "include loop" may be seen by tracing
the preprocessor activity:
C // file C.cc
C #include <A>
A // file <A>
A #ifndef A
A #define A 1
A #include <B>
B // file <B>
B #ifndef B
B #define B 1
B #include <A>
A // file <A>
A #ifndef A <-- oops, cpp symbol A defined already
A ... <-- skip <A> contents
A #endif
B typedef int B_type;
B extern A_type f(B_type); <-- error, A_type not defined yet.
B #endif /* B */
A typedef int A_type;
A extern B_type g(A_type);
A #endif /* A */
The main symptom of #include loops is that definitions from file <A>
are not available after the #include <A> for certain include orders.
The number of standard headers makes testing all permutations of
include order impractical, so a policy is needed to prevent chaos.
In any case, for some standard headers (as for the above) no ordering
can eliminate the loop.
Other factors influence the policy. Typical implementations of
Make (unfortunately including GNU make) have bugs relating to file
names with no suffix, that lead to such problems as failure to track
dependencies on such files and an inclination to _delete_ them.
Therefore, headers used in building the library are always of the
form <bits/yyy.h> generally, or specifically <bits/std_xxx.h> for
an equivalent to the standard header <xxx>.
Standard headers <xxx> are all placed under directory std/, and
are ignored except during installation. These headers simply
#include the corresponding header <bits/std_xxx.h>.
Standard substitute headers <bits/std_xxx.h> that have any complexity
may sub-include other headers. When they sub-include non-standard
headers, they first include all the headers required for that
non-standard header.
Mutual dependencies are handled by splitting up the declarations
intended for standard headers among two or more files, and then
interleaving them as needed. For example, we replace <A> and <B>
above, as follows:
// file <bits/std_A.h>
#ifndef _CPP_A
#define _CPP_A
# include <bits/A_types.h>
# include <bits/B_types.h>
# include <bits/A_funs.h>
#endif
// file <bits/std_B.h>
#ifndef _CPP_B
#define _CPP_B
# include <bits/A_types.h>
# include <bits/B_types.h>
# include <bits/B_funs.h>
#endif
// file <bits/A_types.h>
#ifndef _CPP_BITS_A_TYPES_H
#define _CPP_BITS_A_TYPES_H
typedef int A_type;
#endif
// file <bits/B_types.h>
#ifndef _CPP_BITS_B_TYPES_H
#define _CPP_BITS_B_TYPES_H
typedef int B_type;
#endif
// file <bits/A_funs.h>
#ifndef _CPP_BITS_A_FUNS_H
#define _CPP_BITS_A_FUNS_H
extern B_type g(A_type);
#endif
// file <bits/B_funs.h>
#ifndef _CPP_BITS_B_FUNS_H
#define _CPP_BITS_B_FUNS_H
extern A_type f(B_type);
#endif
Of course we have the standard headers under their mandated names:
// file <std/A>
#ifndef _CPP_A
#define _CPP_A
# include <bits/std_A.h>
#endif
// file <std/B>
#ifndef _CPP_B
#define _CPP_B
# include <bits/std_B.h>
#endif
Notice that the include guards are named uniformly except that
the guard for standard header <bits/std_A.h> is just _CPP_A,
identically as the header <A> in std/.
At installation the files std/* can be replaced by symbolic links,
or simply copied into place as is. The result is:
include/
include/A -> bits/std_A.h
include/B -> bits/std_A.h
include/bits/
include/bits/std_A.h
include/bits/std_B.h
include/bits/A_types.h
include/bits/B_types.h
include/bits/A_funs.h
include/bits/B_funs.h
Of course splitting up standard headers this way creates
complexity, so it is not done routinely, but only in response
to discovered needs.
Another reason to split up headers is for support of separate
compilation of templates. This interacts with the foregoing
because template definitions typically have many more dependencies
on other headers than do pure declarations. Non-inline template
definitions are placed in a separate ".tcc" file that is included
by the standard header, and any other standard header that
requires definitions from it for its implementation.
The key to preventing chaos, given the above structure, is:
Only standard headers <bits/std_xxxx.h> should sub-include
other headers.