Clean up your #include statements?

14,167

Solution 1

To verify that header files are including everything they need, I would creating a source file that all it does is include a header file and try to compile it. If the compile fails, then the header file itself is missing an include.

You get the same effect by making the following rule: that the first header file which foo.c or foo.cpp must include should be the correspondingly-named foo.h. Doing this ensures that foo.h includes whatever it needs to compile.

Furthermore, Lakos' book Large-Scale C++ Software Design (for example) lists many, many techniques for moving implementation details out of a header and into the corresponding CPP file. If you take that to its extreme, using techniques like Cheshire Cat (which hides all implementation details) and Factory (which hides the existence of subclasses) then many headers would be able to stand alone without including other headers, and instead make do with just forward declaration to opaque types instead ... except perhaps for template classes.

In the end, each header file might need to include:

  • No header files for types which are data members (instead, data members are defined/hidden in the CPP file using the "cheshire cat" a.k.a. "pimpl" technique)

  • No header files for types which are parameters to or return types from methods (instead, these are predefined types like int; or, if they're user-defined types, then they're references in which case a forward-declared, opaque type declaration like merely class Foo; instead of #include "foo.h" in the header file is sufficient).

What you need then is the header file for:

  • The superclass, if this is a subclass

  • Possibly any templated types which are used as method parameters and/or return types: apparently you're supposed to be able to forward-declare template classes too, but some compiler implementations may have a problem with that (though you could also encapsulate any templates e.g. List<X> as implementation details of a user-defined type e.g. ListX).

In practice, I might make a "standard.h" which includes all the system files (e.g. STL headers, O/S-specific types and/or any #defines, etc) that are used by any/all header files in the project, and include that as the first header in every application header file (and tell the compiler to treat this "standard.h" as the 'precompiled header file').


//contents of foo.h
#ifndef INC_FOO_H //or #pragma once
#define INC_FOO_H

#include "standard.h"
class Foo
{
public: //methods
  ... Foo-specific methods here ...
private: //data
  struct Impl;
  Impl* m_impl;
};
#endif//INC_FOO_H

//contents of foo.cpp
#include "foo.h"
#include "bar.h"
Foo::Foo()
{
  m_impl = new Impl();
}
struct Foo::Impl
{
  Bar m_bar;
  ... etc ...
};
... etc ...

Solution 2

I have the habit of ordering my includes from high abstraction level to low abstraction level. This requires that headers have to be self-sufficient and hidden dependencies are quickly revealed as compiler errors.

For example a class 'Tetris' has a Tetris.h and Tetris.cpp file. The include order for Tetris.cpp would be

#include "Tetris.h"     // corresponding header first
#include "Block.h"      // ..then application level includes
#include "Utils/Grid.h" // ..then library dependencies
#include <vector>       // ..then stl
#include <windows.h>    // ..then system includes

And now I realize this doesn't really answer your question since this system does not really help to clean up unneeded includes. Ah well..

Solution 3

Depending on the size of your project, looking at the include graphs created by doxygen (with the INCLUDE_GRAPH option on ) can be helpful.

Solution 4

Detecting superfluous includes has already been discussed in this question.

I'm not aware of any tools to help detect insufficient-but-happens-to-work includes, but good coding conventions can help here. For example, the Google C++ Style Guide mandates the following, with the goal of reducing hidden dependencies:

In dir/foo.cc, whose main purpose is to implement or test the stuff in dir2/foo2.h, order your includes as follows:

  1. dir2/foo2.h (preferred location — see details below).
  2. C system files.
  3. C++ system files.
  4. Other libraries' .h files.
  5. Your project's .h files.

Solution 5

One big problem with the remove a header and recompile technique is that it can lead to still-compiling, but wrong or inefficient code.

  1. Template specialization: If you have a template specialization for a specific type that is in one header and the more general template in another, removing the specialization may leave the code in a compilable state, but with undesired results.

  2. Overload resolution: A similar issue - if you have two overloads of one function in different headers, but that take somewhat compatible types, you can end up removing the version that is the better fit in one case, but still have the code compile. This is probably less likely than the template specialization version, but it is possible.

Share:
14,167
criddell
Author by

criddell

Updated on June 26, 2022

Comments

  • criddell
    criddell almost 2 years

    How do you maintain the #include statements in your C or C++ project? It seems almost inevitable that eventually the set of include statements in a file is either insufficient (but happens to work because of the current state of the project) or includes stuff that is no longer needed.

    Have you created any tools to spot or rectify problems? Any suggestions?

    I've been thinking about writing something that compiles each non-header file individually many times, each time removing an #include statement. Continue doing this until a minimal set of includes is achieved.

    To verify that header files are including everything they need, I would create a source file that all it does is include a header file and try to compile it. If the compile fails, then the header file itself is missing an include.

    Before I create something though, I thought I should ask here. This seems like a somewhat universal problem.