Is an #include before #ifdef/#define Include-Guard okay?

Learn is an #include before #ifdef/#define include-guard okay? with practical examples, diagrams, and best practices. Covers c++, include, header-files development techniques with visual explanations.

Is an #include Before #ifdef/#define Include-Guard Okay?

Hero image for Is an #include before #ifdef/#define Include-Guard okay?

Explore the implications of placing an #include directive before an include guard in C++ header files, understanding potential issues and best practices for robust code.

Include guards are a fundamental mechanism in C and C++ to prevent multiple inclusions of the same header file within a single compilation unit. They typically involve a #ifndef, #define, and #endif block. A common question arises regarding the placement of other preprocessor directives, specifically #include, relative to these guards. This article delves into whether placing an #include before the include guard is acceptable, the problems it might cause, and the recommended practices.

Understanding Include Guards

Include guards work by defining a unique preprocessor macro the first time a header file is processed. Subsequent attempts to include the same header will find this macro already defined, causing the preprocessor to skip the entire content of the header until the #endif. This prevents redefinitions of types, functions, and variables, which would otherwise lead to compilation errors.

// Recommended structure for a header file (my_header.h)
#ifndef MY_HEADER_H
#define MY_HEADER_H

// All header content goes here
#include <vector>

class MyClass {
public:
    void doSomething();
};

#endif // MY_HEADER_H

Standard include guard implementation

The Problem with #include Before Guards

Placing an #include directive before the include guard can lead to subtle and hard-to-debug issues. The primary problem is that the content of the pre-guard #include will always be processed, regardless of whether the main header file has already been included. This defeats the purpose of the include guard for that specific included file and can lead to multiple definition errors if the pre-guard included file itself lacks proper guards or contains definitions.

flowchart TD
    A[Source File .cpp]
    B[#include "my_header.h"]
    C[my_header.h]
    D[#include <dependency.h>]
    E[#ifndef MY_HEADER_H]
    F[#define MY_HEADER_H]
    G[Header Content]
    H[#endif]

    A --> B
    B --> C
    C --> D
    D --> E
    E -- No --> H
    E -- Yes --> F
    F --> G
    G --> H

    subgraph Problematic Scenario
        C_problem[my_header.h]
        D_problem[#include <dependency.h>]
        E_problem[#ifndef MY_HEADER_H]
        F_problem[#define MY_HEADER_H]
        G_problem[Header Content]
        H_problem[#endif]

        C_problem --> D_problem
        D_problem --> E_problem
        E_problem -- No --> H_problem
        E_problem -- Yes --> F_problem
        F_problem --> G_problem
        G_problem --> H_problem
    end

    style D_problem fill:#f9f,stroke:#333,stroke-width:2px
    linkStyle 3 stroke:#f00,stroke-width:2px,fill:none;
    linkStyle 4 stroke:#0f0,stroke-width:2px,fill:none;
    linkStyle 5 stroke:#0f0,stroke-width:2px,fill:none;
    linkStyle 6 stroke:#0f0,stroke-width:2px,fill:none;
    linkStyle 7 stroke:#0f0,stroke-width:2px,fill:none;
    linkStyle 8 stroke:#0f0,stroke-width:2px,fill:none;
    linkStyle 9 stroke:#f00,stroke-width:2px,fill:none;

    A -- Multiple inclusions --> B_second[#include "my_header.h"]
    B_second --> C_problem
    C_problem -- Always processed --> D_problem
    D_problem -- Can cause redefinition --> E_problem

    classDef problematic fill:#f9f,stroke:#333,stroke-width:2px;
    class D_problem problematic;
    class D problematic;
    class E problematic;
    class F problematic;
    class G problematic;
    class H problematic;
    class C_problem problematic;
    class E_problem problematic;
    class F_problem problematic;
    class G_problem problematic;
    class H_problem problematic;
    class B_second problematic;
    class C problematic;
    class B problematic;
    class A problematic;

Flowchart illustrating the problematic scenario of #include before include guards

Edge Cases and Exceptions

While generally discouraged, there are very specific, rare scenarios where an #include before an include guard might be considered. One such case is when the included file defines macros that are intended to control the behavior of the include guard itself, or when it defines a macro that is part of the include guard name. However, such practices are highly unconventional and can severely reduce code readability and maintainability. It's almost always better to define such controlling macros externally or refactor the header structure.

// A highly discouraged, problematic example
#include "version_macros.h" // Defines MY_LIB_VERSION

#ifndef MY_HEADER_V_##MY_LIB_VERSION_H
#define MY_HEADER_V_##MY_LIB_VERSION_H

// ... header content ...

#endif // MY_HEADER_V_##MY_LIB_VERSION_H

An example of a problematic pre-guard include for conditional compilation

Best Practices for Header Inclusion

The universally accepted best practice is to place all #include directives inside the include guard. This ensures that all dependencies are processed only once when the header is first included, adhering to the principle of least surprise and maximizing compilation efficiency. This also makes the header's dependencies explicit and contained within its guarded scope.

1. Define Unique Guard

Start your header file with a unique #ifndef and #define pair. A common convention is PROJECT_DIRECTORY_FILENAME_H (e.g., MYPROJECT_HEADERS_MYCLASS_H).

2. Include Dependencies

Place all necessary #include directives immediately after your #define guard. This includes both standard library headers and other project headers.

3. Add Declarations/Definitions

Follow with your class declarations, function prototypes, constant definitions, and any other content that belongs in the header.

4. Close Guard

End your header file with #endif corresponding to your initial #ifndef.