Self-contained header files in C/C++
Asked Answered
U

8

32

I recently posted a question asking for what actions would constitute the Zen of C++. I received excellent answers, but I could not understand one recommendation:

  • Make header files self-contained

How do you ensure your header files are self-contained?

Any other advice or best-practice related to the design and implementation of header files in C/C++ will be welcome.

Edit: I found this question which addresses the "Best Practices" part of mine.

Underage answered 12/12, 2009 at 2:23 Comment(1)
The 'Zen of C++' question referenced has been deleted; AFAIK, it means that only those with a rep of more than 10K can see it still.Ramrod
E
43

A self-contained header file is one that doesn't depend on the context of where it is included to work correctly. If you make sure you #include or define/declare everything before you use it, you have a self-contained header.
An example of a non self-contained header might be something like this:

----- MyClass.h -----

class MyClass
{
    MyClass(std::string s);
};
---- MyClass.cpp -----

#include <string>
#include "MyClass.h"
    
MyClass::MyClass(std::string s)
{}

In this example, MyClass.h uses std::string without first #including <string>. For this to work, in MyClass.cpp you need to put the #include <string> before #include "MyClass.h".
If MyClass's user fails to do this he will get an error that std::string is not included.

Maintaining your headers to be self-contained can be often neglected. For instance, you have a huge MyClass header and you add to it another small method which uses std::string. If in all places this class is currently used, <string> is already #included before MyClass.h, then someday you will #include MyClass.h as the first header and suddenly you have this new error in a file you didn't even touch (MyClass.h).
Carefully maintaining your headers to be self-contained helps to avoid this problem.

Emden answered 12/12, 2009 at 2:30 Comment(5)
@shoosh: is it bad when different header files call the same, third, header file? For instance, if two .h files call math.h.Underage
@Arrieta: See jeremyosborne's answer. System header files generally guard against being included twice in this way, and so should yours.Willard
Another factor making this difficult is the "system headers can include other headers" rule in C++. If <iostream> includes <string>, then it's quite difficult to discover that you've forgotten to include <string> in some header which does use <iostream>. Compiling the header on its own gives no errors: it's self-sufficient on this version of your compiler, but on another compiler it might not work.Burnley
@Steve: I gave you a +1 comment even though I think you meant "in some header which does not use <iostream>. This issue is why I parenthesized '(reliably)' in my answer.Ramrod
Correct that I typed the wrong thing. I actually meant to say, "does include <iostream>". Header A includes <iostream> but does not include <string>. Header A uses std::string. On implementation B (where <iostream> includes <string>), A appears self-sufficient. On implementation C (where <iostream> does not include <string>), A is shown not to be self-sufficient.Burnley
R
27

NASA's Goddard Space Flight Center (GSFC) has published C and C++ programming standards that address this issue.

Assume you have a module with a source file perverse.c and its header perverse.h.

Ensuring a header is self-contained

There is a very simple way to ensure that a header is self-contained. In the source file, the first header you include is the module's header. If it compiles like this, the header is self-contained (self-sufficient). If it does not, fix the header until it is (reliably1) self-contained.

perverse.h

#ifndef PERVERSE_H_INCLUDED
#define PERVERSE_H_INCLUDED

#include <stddef.h>

extern size_t perverse(const unsigned char *bytes, size_t nbytes);

#endif /* PERVERSE_H_INCLUDED */

Almost all headers should be protected against multiple inclusion. (The standard <assert.h> header is an explicit exception to the rule — hence the 'almost' qualifier.)

perverse.c

#include "perverse.h"
#include <stdio.h>   // defines size_t too

size_t perverse(const unsigned char *bytes, size_t nbytes)
{
    ...etc...
}

Note that even though it was traditionally considered a good idea to include the standard headers before the project headers, in this case, it is crucial to the testability that the module header (perverse.h) comes before all others. The only exception I'd allow is including a configuration header ahead of the module header; however, even that is dubious. If the module header needs to use (or maybe just 'can use') the information from the configuration header, it should probably include the configuration header itself, rather than rely on the source files using it to do so. However, if you need to configure which version of POSIX to request support for, that must be done before the first system header is included.


Footnote 1: Steve Jessop's comment to Shoosh's answer is why I put the parenthesized '(reliably)' comment into my 'fix it' comment. He said:

Another factor making this difficult is the "system headers can include other headers" rule in C++. If <iostream> includes <string>, then it's quite difficult to discover that you've forgotten to include <string> in some header which does [not] use <iostream> [or <string>]. Compiling the header on its own gives no errors: it's self-sufficient on this version of your compiler, but on another compiler it might not work.

See also the answer by Toby Speight about IWYU — Include What You Use.


Appendix: Matching these rules with GCC Precompiled Headers

The GCC rules for precompiled headers permit just one such header per translation unit, and it must appear before any C tokens.

GCC 4.4.1 Manual, §3.20 Using Precompiled Headers

A precompiled header file can be used only when these conditions apply:

  • Only one precompiled header can be used in a particular compilation.
  • A precompiled header can’t be used once the first C token is seen. You can have preprocessor directives before a precompiled header; you can even include a precompiled header from inside another header, so long as there are no C tokens before the #include.
  • [...]
  • Any macros defined before the precompiled header is included must either be defined in the same way as when the precompiled header was generated, or must not affect the precompiled header, which usually means that they don’t appear in the precompiled header at all.

To a first approximation, these constraints mean that the precompiled header must be the first in the file. A second approximation notes that if 'config.h' only contains #define statements, it could appear ahead of the precompiled header, but it is much more likely that (a) the defines from config.h affect the rest of the code, and (b) the precompiled header needs to include config.h anyway.

The projects I work on are not set up to use pre-compiled headers, and the constraints defined by GCC plus the anarchy induced by over 20 years of intensive maintenance and extension by a diverse population of coders mean it would be very hard to add them.

Given the divergent requirements between the GSFC guidelines and GCC precompiled headers (and assuming that precompiled headers are in use), I think that I would ensure the self-containment and idempotence of headers using a separate mechanism. I already do this for the main projects I work on — reorganizing the headers to meet the GSFC guidelines is not an easy option — and the script I use is chkhdr, shown below. You could even do this as a 'build' step in the header directory — ensure that all the headers are self-contained as a 'compilation' rule.

chkhdr script

I use this chkhdr script to check that headers are self-contained. Although the shebang says 'Korn shell', the code is actually OK with Bash or even the original (System V-ish) Bourne Shell.

#!/bin/ksh
#
# @(#)$Id: chkhdr.sh,v 1.2 2010/04/24 16:52:59 jleffler Exp $
#
# Check whether a header can be compiled standalone

tmp=chkhdr-$$
trap 'rm -f $tmp.?; exit 1' 0 1 2 3 13 15

cat >$tmp.c <<EOF
#include HEADER /* Check self-containment */
#include HEADER /* Check idempotency */
int main(void){return 0;}
EOF

options=
for file in "$@"
do
    case "$file" in
    (-*)    options="$options $file";;
    (*)     echo "$file:"
            gcc $options -DHEADER="\"$file\"" -c $tmp.c
            ;;
    esac
done

rm -f $tmp.?
trap 0

It so happens that I've never needed to pass any options containing spaces to the script so the code is not sound in its handling of options of spaces. Handling them in Bourne/Korn shell at least makes the script more complex for no benefit; using Bash and an array might be better.

Usage:

chkhdr -Wstrict-prototypes -DULTRA_TURBO -I$PROJECT/include header1.h header2.h

GSFC Standard available via Internet Archive

The URL linked above is no longer functional (404). You can find the C++ standard (582-2003-004) at EverySpec.com (on page 2); the C standard (582-2000-005) seems to be missing in action.

However, the referenced NASA C coding standard can be accessed and downloaded via the Internet archive:

http://web.archive.org/web/20090412090730/http://software.gsfc.nasa.gov/assetsbytype.cfm?TypeAsset=Standard

See also:

Ramrod answered 12/12, 2009 at 4:49 Comment(10)
Ah, those Goddard evil overlords. I will check it out.Underage
Now I wish there was a way to "marry" this principle with precompiled headers, which also need to come first thing. Any tips?Whortleberry
@romkyns: see the appendix added.Ramrod
The link for "NASA's Goddard Space Flight Center (GSFC) C and C++ programming standards" seems broken. Can you provide a formal reference, at least?Pliers
@TobySpeight — I've modified that section a bit. There was an era (back in the 90s, maybe earlier) when "system headers first" was relatively common advice, but it probably does need some modification now. The IWYU stuff is interesting too, and interacts with all this — if a project header currently includes a system header, but your code uses both the features described by the project header and the directly uses the features from the system header, should you explicitly include both? There's a case for "yes". […continued…]Ramrod
[…continuation…] Also, as long as at least one source file tests the self-containment of a header (the source files that define the features described by the header should do this), it doesn't matter if other files include system headers before the project headers or not.Ramrod
Thanks for updating this old answer; I think it's even better now!Itch
@JonathanLeffler - I'm having some trouble understanding your 'chkhdr' script. I've run it on some headers as a test in a big project I'm working on and all it seems to do is print out the names, on their own line, with a colon afterward. I don't have a great background in bash scripting so if you could add some more comments to the bash (ksh) scripts you wrote I would very much appreciate it!Lusk
@Lusk — That means that there's no problem with those headers (probably). If there was a problem, you'd get compiler error messages diagnosing the problem. Near silence is the "OK" indication. Copy one of your test headers to junk.h; edit junk.h so that the #ifndef HEADER_H is misspelled — e.g. #iiffnnddeeff HEADER_H; save it; run chkhdr junk.h — see the difference. Alternatively, it might mean that your headers contain no useful code under the compilation options you're using. For example, perhaps it contains #ifdef TURBO#endif and you don't use chkhdr -DTURBO hdr.h...Ramrod
@z.karl: you can probably use chkhdr -E hdr.h to see what the preprocessor is generating. If that's mostly blank, you might well need the equivalent of -DTURBO to test the content. At that point, you have to look at the header to determine how to get its contents compiled — and then use the right command line. That can be an iterative process, sometimes. (I was using it to track down duplicate declarations in a single header earlier this week; it took me four or five attempts to get the right -I and -D options in place, plus -Wredundant-decls — but I got lots of errors to guide me.)Ramrod
S
6

Make sure you include everything you need in the header, instead of assuming that something you included includes something else you need.

Shuman answered 12/12, 2009 at 2:30 Comment(0)
I
5

Old question, new answer. :-)

There is now a tool called include-what-you-use which is designed to analyse your code for exactly this kind of problem. On Debian and derived systems, it can be installed as the iwyu package.

Itch answered 2/6, 2015 at 17:9 Comment(1)
Thanks --- interesting tool.Underage
G
3

The idea is that a header file does not depend on a previous header file in order to compile. Therefore the order of the header files is not significant. Part of doing this is including in a header file all the other header files it will need. The other part is ifdef'ing your headers so that they aren't processed more than once.

The idea is that if you need to add a foo object to your class you just need to #include foo.h and you don't need to bar.h in front of it in order to get foo.h to compile (e.g. there is a call in foo that returns a bar object instance. You may not be interested in this call but you will need to add bar.h to let the compiler know what is being referenced).

I'm not sure I would always agree with this advice. A large project will have hundreds of header files and the compile will end up reading through the common ones of them hundreds of times just to ignore the #ifdefs. What I have seen done in this case is a header file of header files that is standard for the project and includes the thirty common ones. It is always first in the list of includes. This can speed up compile time but makes the maintenance of the general header a skilled task.

Ghiselin answered 12/12, 2009 at 2:43 Comment(0)
C
2

You'd want to use the method described in the GNU C Preprocessor Manual:

2.4 Once-Only Headers

If a header file happens to be included twice, the compiler will process its contents twice. This is very likely to cause an error, e.g. when the compiler sees the same structure definition twice. Even if it does not, it will certainly waste time.

The standard way to prevent this is to enclose the entire real contents of the file in a conditional, like this:

/* File foo.  */
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN

the entire file

#endif /* !FILE_FOO_SEEN */

This construct is commonly known as a wrapper #ifndef. When the header is included again, the conditional will be false, because FILE_FOO_SEEN is defined. The preprocessor will skip over the entire contents of the file, and the compiler will not see it twice.

CPP optimizes even further. It remembers when a header file has a wrapper ‘#ifndef’. If a subsequent ‘#include’ specifies that header, and the macro in the ‘#ifndef’ is still defined, it does not bother to rescan the file at all.

You can put comments outside the wrapper. They will not interfere with this optimization.

The macro FILE_FOO_SEEN is called the controlling macro or guard macro. In a user header file, the macro name should not begin with ‘_’. In a system header file, it should begin with ‘__’ to avoid conflicts with user programs. In any kind of header file, the macro name should contain the name of the file and some additional text, to avoid conflicts with other header files.

Cradlesong answered 12/12, 2009 at 3:38 Comment(0)
M
1

This is a great question. I think I will re-examine the practice of putting a stdafx.h as the first include in each .cpp file when using Visual Studio. If you use pre-compiled header files, it doesn't matter anyway, might as well have friendlier header files.

Thanks jalf for correction. From Wikipedia

Visual C++ will not compile anything before the #include "stdafx.h" in the source file, unless the compile option /Yu'stdafx.h' is unchecked (by default); it assumes all code in the source up to and including that line is already compiled.

So this means that pre-compiled headers break the self-contained header rule, right?

Midlands answered 12/12, 2009 at 4:59 Comment(2)
With precompiled headers it does matter. The assumption that the precompiled header is the first one included is what makes it possible. Include another header first, and you change the compiler state, and it all comes crashing down. Which is one reason I don't much like precompiled headers. It's such a clumsy, fragile solution. Definitely not something I'd use "by default". Only when compile times force me to do it.Entropy
@Entropy unfortunately my own experience is that the moment you include significant portions of boost, std or windows (i.e. almost any windows app larger than "tiny") you need precompiled headers :/Whortleberry
P
0

Having not seen your other question, my first thought around this would be protecting my header files from multiple calls (let my headers fend for themselves).

#ifndef MY_PROTECTED_HEADER_H
#define MY_PROTECTED_HEADER_H
/*
 * Stuff here
 */
#endif /* MY_PROTECTED_HEADER_H */
Pitsaw answered 12/12, 2009 at 2:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.