“Master” Header Pitfalls

It’s sometimes surprising how poorly-understood the C++ build process is. The preprocessor and its quirks seems to be particularly opaque to many, leading to the adoption of some harmful preprocessor practices. There’s one issue I’m thinking of in particular right now, mostly because there’s been a rash of users running in to trouble with it over at GDNet: the use of so-called “master” (or “global” or “yes, even the kitchen sink”) headers.

Master headers are those header files that are included by every other header file in the project, and usually include all the “common,” “basic,” or “required,” headers in your project. They are occasionally viewed as beneficial in that they decrease the amount of typing you need to do to build a new component header file (there’s only one header to include), and they increase the clarity of that header file by hiding all those nasty #include directives behind one single, clean directive instead. While this might be true, what you can end up paying for those benefits far outweighs the value of the benefits themselves.

There are many small problems with master headers, but there are three that I consider to be particularly painful.

  • When the master include (or anything included by the master include) is changed, all files including the master are out of date and may need to be recompiled. For a project of sufficient size and sufficient master include proliferation, this can kill your build time, as you must essentially rebuild the entire project every time you change a header, even if that header is only actually relevant to a small subset of source files.
  • They introduce latent, implicit dependancies. That is, since a source file that includes the master is effectively including every file included by the master, it is easy for code to be added to that source file that uses something defined in any of those headers. The source file is thus dependant on that header, but that dependency is not explicit (the header was not included directly), and it may cross subsystems in an unacceptable way.
  • They can render seemingly legal code illegal in subtle ways.

That last point bears some explaination, as its one of the trickier problems with master includes, and usually the one that trips people up enough to finally convince them to revise their include system. Let’s say you’re writing a game, and you have a master include file called GlobalIncludes.h.

1
2
3
4
5
6
7
8
//GlobalIncludes.h
#ifndef GLOBAL_INCLUDES_H_
#define GLOBAL_INCLUDES_H_
 
#include "BaseObject.h"
#include "NiftyPowerUp.h"
 
#endif

GlobalIncludes.h includes all the headers for your game framework, which for our purposes will be only BaseObject.h (the base class of all objects) and NiftyPowerup.h (the class for a powerup that makes the player nifty). We also have corresponding source files for each header.

<td cl