«

»

Jun
15
2012

Code Better: Headers without Hidden Dependencies

When you work on a larger project, you cannot easily keep track of which header depends on which other header. You can (and should) do your best to keep the number of other headers referenced inside your headers low (to speed up compilation) and move as many header dependencies as you can into your source files, but this still doesn’t prevent you from building headers that implicitly depend on another header being included before them.

Take this example:

#ifndef MAP_H
#define MAP_H

/// <summary>Stores the tiles of a 2D map<summary>
struct Map {};

#endif // MAP_H
#ifndef WORLD_H
#define WORLD_H

#include "Actor.h"
#include <vector>
// Oops, forgot Map.h, but won't notice since World.cpp includes Map.h before World.h

/// <summary>Maintains the state of the entire world<summary>
struct World {
  /// <summary>Stores the map as a grid of 2D tiles</summary>
  public: Map Map;
  /// <summary>Actors (player, monsters, etc.) currently active in the world</summary>
  public: std::vector<Actor *> Actors;
};

#endif // WORLD_H

Throughout your project, map.h might always end up being included before world.h and you might never notice that if someone included world.h on its own, a nasty compilation error would be the result.

So what can you do ensure this situation never happens?

Solution

It’s actually pretty simple: each header should have an associated source file. And the first thing that source file should do is to include its associated header. Always.

  • If a header just declares a silly enumeration — add a source file that does nothing else but include the header.
  • If a header just provides a convenient selection of other headers — add a new source file that does nothing else but include that header.
  • And if a header declares a class with many methods whose method bodies are in a source file that source file — you guessed it — should include its own header first thing.

This ensures that any header in your entire project is at least once included before any other headers, thus, if any header on its own produced a compilation error, you would catch it while compiling your project.

I’ve designed entire frameworks and games following this rule and it eliminated all hidden dependencies from my header, resulting in nothing but good, usable design!

6 comments

  1. glue says:

    Good post. I’ve only worked on a few C++ projects and something I’ve always struggled with is knowing how to structure the project to keep compile times low and prevent errors. This is just another piece of the puzzle. I would love to hear more of your opinions on this topic

  2. Cygon says:

    Thanks!

    I’d love to write more about directory layouts, API design and architecture, but all three are topics where I’m constantly struggling to formulate concrete advice that I could write down in an article and that someone else could pick up. I’ve got 2 other posts in the queue already that I’ve been rewriting and editing for months now.

    If I ever manage to distill something useful out of them I’ll publish them here :)

  3. Rui Pires says:

    Good post.
    Anyone with some experience with C++ has learned this, either the easy way (be reading it in a nice blog post) or the hard way (nasty compilation errors and long compile times).

    A good way to detect this is to “force” yourself to write unit tests. Since the tests should include only the class to be tested, this kind of architectural problems are detected sooner.

    Cheers,
    Rui

  4. Cygon says:

    I didn’t know this was common advice — if it is, I haven’t yet seen any project where it is consistently applied, including Boost :)

    Unit testing is a good point, it really helps trimming those dependencies down since one quickly gets annoyed if just creating an instance of the class being testing requires initializing or mocking 10 other systems.

  5. Darren Evans says:

    Excellent advice.

    I heard that many compilers still try to open and load a file even if a header file includes header guards. Is this true of Visual Studio? Does the #pragma once directive exhibit this behavior?

    This raises the question of good advice for the physical structure of Visual Studio solution and project files and directories. Does anyone have good advice with regards to this or is it too subjective a topic?

  6. Cygon says:

    In the past, #pragma once caused Visual C++ to cache the name of the header it appeared in so that it wouldn’t load the same header multiple times in the same compilation unit (.cpp file), but it was not portable.

    Instead of copying #pragma once, GCC was equipped with the capability to recognize header guards (and why not, the logic is quite simple, if there’s no compilable code in a file after it ran through the preprocessor twice, then it had a header guard) and achieved the same performance benefit without proprietary pragmas.

    Eventually GCC implemented #pragma once still and Visual C++ became capable of recognizing header guards, so the only advantage #pragma once has these days is that it’s shorter.

    I’m only still using normal header guards because it’s closer to ISO C++ and causes less trouble with other C++ parsers like Doxygen :)

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please copy the string s0AbW7 to the field below:

Social Widgets powered by AB-WebLog.com.