3

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?

4

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.

  • How about what I added to my question? – Karlovsky120 Oct 4 '18 at 14:47
  • @Karlovsky120 as already mentioned, it is totally legitimate if a header “re-exports” declarations from another header. With current C/C++, there is no way to tell what the header author's intention was. This is part of what modules may offer: with C++ modules every header might be its own translation unit, and explicitly exports certain symbols. – amon Oct 4 '18 at 14:50
  • I have to read up on that still then. Thanks for clarifying! – Karlovsky120 Oct 4 '18 at 14:52
1

A rule that you should follow in languages like C, C++, Objective-C is that every single header file compiles on its own. This is achieved here (so someone including bottom.h doesn’t need to remember to include top.h an string).

In practice, the case of removing a header like string from top.h is rare, the compiler tells you, you add string to bottom.h in five seconds, everything is fine and you don’t worry about it. It is of course up to the developer changing top.h to fix everything that breaks.

  • I added an idea to my question, would that work? – Karlovsky120 Oct 4 '18 at 14:46
0

Use unit tests.

You arrange your unit test code so that each unit test/suite runs in a separate translation unit. This will mean that each header gets pulled and trialled in one or more alternative programs which will have different namespace ecosystems - thus exposing assumptions about symbols that have been defined.

  • I added an idea to my question. – Karlovsky120 Oct 4 '18 at 14:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.