Search code examples
c++c++11includeheader-only

Header-only library design - include directives


I'm creating an header-only C++11/14 library and I'm not sure on how I should handle #include directives between library files.

Should I try to group as many #include directives as possible in the user-oriented module header file or should internal files include files they require (sometimes repeating the same includes)?


Approach A:

In this approach, the module header file includes all required dependencies and then includes the implementations. The implementations' header files do not include anything by themselves.

// Library/Module/Module.hpp
// This file is intended to be included by the user in his projects.

#ifndef MODULE
#define MODULE

#include <vector>
#include "Library/Module/Impl/SharedDependency.hpp"
#include "Library/Module/Impl/Class1.hpp"
#include "Library/Module/Impl/Class2.hpp"

#endif MODULE

-

// Library/Module/Impl/SharedDependency.hpp

#ifndef SHARED_DEPENDENCY
#define SHARED_DEPENDENCY

inline void sharedFunc() { }

#endif

-

// Library/Module/Impl/Class1.hpp

#ifndef CLASS1
#define CLASS1

// No need to include "SharedDependency.hpp", as it will be included by
// the module header file. Same applies for <vector>.
struct Class1 
{ 
    std::vector<int> v;        
    Class1() { sharedFunc(); } 
};

#endif

-

// Library/Module/Impl/Class2.hpp

#ifndef CLASS2
#define CLASS2

// No need to include "SharedDependency.hpp", as it will be included by
// the module header file. Same applies for <vector>.
struct Class2
{ 
    std::vector<int> v;        
    Class2() { sharedFunc(); } 
};

#endif


Approach B:

In this approach, the module header file includes only the implementation headers. If the implementation headers require additional includes, they include the files themselves (recursively), sometimes repeating the same include.

// Library/Module/Module.hpp
// This file is intended to be included by the user in his projects.

#ifndef MODULE
#define MODULE

#include "Library/Module/Impl/Class1.hpp"
#include "Library/Module/Impl/Class2.hpp"

#endif MODULE

-

// Library/Module/Impl/SharedDependency.hpp

#ifndef SHARED_DEPENDENCY
#define SHARED_DEPENDENCY

inline void sharedFunc() { }

#endif

-

// Library/Module/Impl/Class1.hpp

#ifndef CLASS1
#define CLASS1

#include <vector>
#include "Library/Module/Impl/SharedDependency.hpp"

struct Class1
{ 
    std::vector<int> v;        
    Class1() { sharedFunc(); } 
};

#endif

-

// Library/Module/Impl/Class2.hpp

#ifndef CLASS2
#define CLASS2

#include <vector>
#include "Library/Module/Impl/SharedDependency.hpp"

struct Class2
{ 
    std::vector<int> v;        
    Class2() { sharedFunc(); } 
};

#endif

What is the best approach?

Intuitively, I think Approach A is the best, as it avoids repeating the same includes and makes clear what files need to be included before the other files. The biggest drawback is, though, that syntax highlighting stops working in my IDE (QT-Creator), in the implementation files with no include directives.


EDIT:

This question was voted to be closed for the reason "opinion based". I disagree, because in a large header-only project such as my library including files may take a lot of compile time. Therefore, approach A may be faster than approach B, or the opposite.


Solution

  • Approach B is actually the best approach, since including the same header multiple times does not produce any observable compilation time increase, but is advantageous for the following reasons:

    • Modern IDEs can use libclang or proprietary solutions to parse the #include directives and provide code-aware syntax highlighting and autocompletion features.

    • As mentioned by TemplateRex, it becomes much easier to verify a sane build process. CMake, for example, provides macros that automatically generate a test for every header.

    • As mentioned by Alf, it is good practice to have every file include all the headers it depends on - users of the library can then "cherry-pick" header files they require, instead of being unexpectedly force to manually include a parent header.