SelfSufficientHeaders - xlgames-inc/XLE GitHub Wiki
#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 #include
d 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:
- If there is a "FooBar.h", this should be first
-
#include
any headers in the same directory as the .cpp file -
#include
any engine headers
- group them by component
- order the components according to the layer diagram: highest level components come first
- The, standard library headers
- 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 #include
s 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 #define
s 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.