C++ Header Inclusion – Detecting Chains and Dependencies

headersinclude

File Top.h

#include <string>
...some code...

File Bottom.h:

#include "Top.h"

void someFunction() {
  string s = new String();
  ...
}

The Bottom.h does not contain an include for the string header, but the code will work because it's implicitly been included by Top.h

Now, if Top.h changes for any reason not to include string anymore, Bottom.h will break.

I know that it's good practice to have your .h files "include independent", meaning each of them explicitly includes all the headers it uses. However, maintenance of such practice is additional mental task I need to do, which is prone to errors since I'm doing it manually.

Is there a way to have the IDE or compiler check for this? Are there static code analyzers that warn the user that they are using a component that isn't explicitly included in the file it's used in? Is there a compiler flag that checks for this? I've been poking around this and haven't managed to find anything like this, but it doesn't look like to difficult thing to implement, and seems like something that could ensure code stability. Given that, is there a reason that what I'm suggesting would be a bad idea or not work in some cases? If so, which?

Does this make any sense at all?

EDIT after reading some answers:

Could it be possible to make linker/compiler semi-process each file semi-separately?

It would take each file and look at all the headers it includes. Then it goes through that files, but doesn't compile them, only looks which symbols are defined there, excluding their own headers. Then it goes through the file we started with and see if it uses any symbols that it didn't encounter in the previous step. If it does, that means that the symbol wan't included at all
or was included implicitly through header of one of the includes.

Wouldn't that solve the problem, or are there some features of C++ that wouldn't work with this?

Best Answer

This is a common but unsolvable problem with C++. A header provides some declarations. How the header provides these declarations is an implementation detail, and it is completely valid if the header provides a declaration by including another header. This is quite common in header-only libraries, or large libraries such as standard library implementations. It is therefore generally impossible to tell whether an indirect dependency is correct or not.

To minimize unwanted transitive header dependencies, various strategies are available.

As a user of a header:

  • Read the documentation to understand which declarations are supposed to be available from which header. There is really no good workaround for this. Do not over-rely on autocomplete to see whether a declaration is available.

    Linters may be aware of the standard library headers.

  • Order any headers you include by layer: first your own, then any other libraries, and finally standard library headers. When everyone does this, it becomes more likely to find missing dependencies in a included header. It doesn't help if a header includes unnecessary other headers.

As an author of a header file:

  • Write clear documentation on which functionality is supposed to be provided by which headers. If you are writing a library, one public header for the whole library may at least avoid problems with internal dependencies. For a class library, a one-class-per-header design may also be helpful, where the header can be named after the class.

  • Minimize which headers you include. Prefer forward declarations over including a header. You must not forward-declare parts of the standard library, but the C++ standard library provides some forward declarations in <iosfwd>.

  • Only include headers that form a part of your public API, for example as argument types or class member types. If you are including a header to get access to a particular function, prefer to include this in a .cpp file. You can hide class members from your public interface with the pImpl pattern, at a slight loss of efficiency.

    Unfortunately, these techniques are generally inapplicable for header-only libraries and for templates.

  • If possible, test with different library implementations. Different implementations should be compatible when used correctly, but might structure their headers differently – possibly leading to “missing” declarations.

C++ modules should limit the transitive header hell, and previews are available in some compilers. Modules will make it clearer to declare what names are exported by a header/module. But until they become widely available (perhaps already with C++20), we have to rely on the header's documentation.