This article needs a technical review. How you can help.
It is best to avoid including files inside headers, because these files will end up being included indirectly in many translation units.
Bad header dependencies like in the example above are bad because:
- They make builds slower because many translation units pull in thousands of lines of code that will be parsed and not used.
- They make it more likely to have bugs due to side effects like clashing macro names.
If these dependencies add even just 5% build time, it adds up pretty quickly, for example with try-servers that tend to be overloaded with build requests. Chances are that the overhead is more than 5%, though the only way to know is to do things right and compare.
Forward declarations
To avoid including too many files in headers, use forward declarations as much as possible in the headers and include files in the implementations instead.
Some things can't be forward declared
Symbols defined in the namespace of a class cannot be forward declared. For instance:
class A { class Nested { ... }; enum AType { ... }; };
In this example Nested and AType cannot be forward-declared.
So it is best to avoid nesting classes and enums in classes when these are likely to be used by header files as value-type functions parameters or class members, and when the header file that contains them already has dependencies.
Let's take the example of gfxASurface.h. It contains a class gfxASurface that has some nested enums like gfxImageFormat. ImageLayers.h could forward declare gfxASurface but it cannot forward declare gfxASurface::gfxImageFormat. As a result we include gfxASurface.h in Layers.h. By including gfxASurface.h we also include nsThreadUtils.h, etc.
These enums could be declared in a separate file that does not include unrelated code, reducing the "weight" of a dependency to a simple enum value.
Forward declarations and nsRefPtr
It is possible to use a forward declaration of a type T used in nsRefPtr<T>.
To do so you need to make sure that nsRefPtr<T>'s methods never get called explicitly or implicitly in the header, because most of these methods call AddRef or Release on T.
Here is an example:
class Foo; // forward declaration class Bar { Bar(Foo* aFoo) : mFoo(aFoo) // wrong {} void SetFoo(Foo* aFoo) { mFoo = aFoo; // wrong } already_AddRefed<Foo> GimmeYourFoo() { return mFoo.forget(); // wrong } nsRefPtr<Foo> mFoo; // ok }; // no explicit destructor: wrong
The previous example will not allow the use of a forward declaration of Foo because almost every use of nsRefPtr involves calling Foo's methods AddRef and Release.
These methods are typically declared in Foo's header using NS_INLINE_DECL_REFCOUTING(Foo). The important part here is that Bar.h uses methods that are declared in Foo and this means that Bar.h needs to include Foo.h instead of simply forward declaring it. One thing that is not always obvious is that when there is a nsRefPtr as a member of a class Bar, the destruction sequence of Bar will trigger the destructor of nsRefPtr which invokes Foo::Release. If Bar's destructor is not declared in the header and defined in the .cpp file, the compiler will generate an empty destructor implicitly as if it was defined inside the header. This means that the destructor of classes containing nsRefPtrs should be defined in the .cpp file rather than being inlined in the header.
Here is a version of the Foo Bar duo that does not require including Foo.h in Bar.h:
// Bar.h class Foo; class Bar { Bar(Foo* mFoo); ~Bar(); void SetFoo(Foo* aFoo); already_addRefed<Foo> GimmeYourFoo(); };
// Bar.cpp #include "Foo.h" Bar::Bar(Foo* mFoo) : mFoo(aFoo) { } Bar::~Bar() // C++ implicitly calls nsRefPtr's destructor here, which calls Foo::Release. { } void Bar::SetFoo(Foo* aFoo) { mFoo = aFoo; } already_AddRefed<Foo> GimmeYourFoo() { return mFoo.forget(); }
In the later version, Foo.h does not contain any call to Foo::AddRef or Foo::Release, therefore it does not need the full definition of Foo. Of course we need to include Foo.h in Bar.cpp, but at least we avoided including Foo.h in every single translation unit that includes Foo.h, and this often means many.
How to see that a header is included in many translation units?
On *nix systems, the following command can help:
find objdir/ -name '*.o.pp' | xargs grep HeaderName.h | wc -l
With objdir your object directory and HeaderName.h the name of the header you are interested in.
This will parse the dependency files generated by make and count the occurrences of the header's name in it. This is not fully accurate and depends on your target platform, but it gives a good general estimation of the number of translation units depending on your header.