Skip to content

SelfSufficientHeaders

djewsbury edited this page Feb 3, 2015 · 1 revision

#Self Sufficient Headers

Always make headers self-sufficient.

##Self sufficient definition

Let's consider a ".h" header. What happens if we renamed it to ".cpp" and tried to compile it. Would it compile? Or would we get a compile error?

If it compiles, we can consider it "self-sufficient." It means that the header does not require any other dependences to compile. However, some headers will not compile.

A non-self-sufficient header file may require some special conditions to be #included in a .cpp file. Normally this means another header file must be included first. It's quite common for older 'C' libraries to have a special include order -- eg, "you must #include header A before you can #include header B." We want to avoid these kinds of special rules.

It seems easy, but there is a trick. Consider the following code:

// FooBar.h header file
class FooBar
{
public:
  FooBar(std:shared_ptr<Object>);
private:
  std::shared_ptr<Object> _object;
};

What's wrong with the above code? Use of std::shared_ptr<> requires #include <memory>. But that's easy to forget, and this is a common problem. Due to the nature of C++, it's not always easy to tell if a header is self-sufficient. And this is a difficult rule to enforce.

##Header include order

We can try to identify self-sufficiency problems by including header files in a strict order. Imagine we're writing FooBar.cpp:

  1. If there is a "FooBar.h", this should be first
  2. #include any headers in the same directory as the .cpp file
  3. #include any engine headers
  • group them by component
  • order the components according to the layer diagram: highest level components come first
  1. The, standard library headers
  2. Finally, OS headers should come last

For example, EnvironmentScene.cpp might look like:

#include "EnvironmentScene.h"
#include "../Shared/CharactersScene.h"
#include "../Shared/SampleGlobals.h"

#include "../../PlatformRig/PlatformRigUtil.h"

#include "../../SceneEngine/LightDesc.h"
#include "../../SceneEngine/LightingParserContext.h"
#include "../../SceneEngine/Terrain.h"
#include "../../SceneEngine/PlacementsManager.h"
#include "../../SceneEngine/SceneEngineUtility.h"

#include "../../RenderCore/RenderUtils.h"
#include "../../RenderCore/Metal/State.h"
#include "../../RenderCore/Assets/TerrainFormat.h"
#include "../../RenderCore/Assets/ModelFormatPlugins.h"

#include "../../ConsoleRig/Console.h"
#include "../../Math/Transformations.h"
#include "../../Utility/Streams/PathUtils.h"

#include <functional>

Most .h header files have a .cpp file with the same name. That means that most .h header files will be included at the top of at least one .cpp file. That provides a means to check most headers.

This order also helps prevent headers interfering with other headers. For example, if we did #include <functional> at the top, it would hide any self-sufficiency problems related to this header.

In other words, this order maximizes the chance that a problem will cause a compile error.

As an aside, it's also a handy way to remind us of the physical architecture of the engine, each time we author a new .cpp file.

##Order of #includes can change the result

In rare cases, the order of #includes in a .cpp file can change the actual code generated. It's uncommon, but can be annoying sometimes. The most common cases involve <windows.h>, which has many #defines that can cause other headers to fail to compile (like DrawText and min/max).

To avoid problems with these cases, OS and underlying graphics API headers are included as late as possible. There are some cases where <windows.h> or <d3d11.h> must be included from other headers. But these cases are kept as rare as possible.