Tooling[tools]

C++ header files: what goes in them, and what must not

Section
Tooling [tools]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
7 min read
In this answer7 clauses
  1. 1What a header file is, and how to build with one
  2. 2What goes in a header, and what goes in the .cpp
  3. 2.1“multiple definition” and LNK2005: a function body in a header
  4. 3Include guards and #pragma once
  5. 4.h or .hpp, and quotes or angle brackets
  6. 5Include what you use, and break include cycles with a forward declaration
  7. 6Modules, and why headers stay
  8. 7A checklist for every header

Abstract

A C++ header file holds declarations that #include pastes into every .cpp file that needs them, because each .cpp file is compiled on its own. Put declarations, class definitions, templates and inline functions in the header, and the bodies of ordinary functions and the definitions of variables in one .cpp file, because the program may define each of those only once. Guard every header with #pragma once or an #ifndef guard, and give every .cpp file to the compiler.

A small sheet with three blue lines of text, the header, has two arrows leading down to two larger sheets, the source files. Each starts with the same three blue lines, then black lines of its own.
Figure 1 — A header is text pasted into every file that includes it.

A C++ header file holds declarations, and #include pastes its text into every .cpp file that uses them, because each .cpp file is compiled on its own. Put declarations, class definitions, templates and inline functions in the header. Put the bodies of ordinary functions and the definitions of variables in one .cpp file: the program may define each of those only once, and a definition in a header is repeated in every file that includes it. Guard every header with #pragma once or an #ifndef guard.

This answer is written for C++17, which added inline variables (Since When’s C++17 table). The samples were built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and they agreed unless a sentence says otherwise.

What changed: a variable shared by several files is declared extern in the header and defined in one .cpp defined inline in the header.

since C++17

What a header file is, and how to build with one

Each .cpp file, with its headers pasted in, is a translation unit, which the compiler turns into an object file on its own; the linker joins the object files into a program. reading.h declares a type and a function; a declaration says that the function exists and how to call it:

[Example 1:
#pragma once
#include <vector>

struct Reading {
    double value;  // degrees Celsius
};

double average(const std::vector<Reading>& readings);

— end example]

reading.cpp includes it and defines the function, with its body:

[Example 2:
#include "reading.h"

double average(const std::vector<Reading>& readings) {
    if (readings.empty()) {
        return 0;
    }
    double sum = 0;
    for (const Reading& r : readings) {
        sum += r.value;
    }
    return sum / static_cast<double>(readings.size());
}

— end example]

main.cpp includes it and calls the function, knowing only the declaration:

[Example 3:
#include "reading.h"

#include <iostream>
#include <vector>

int main() {
    std::vector<Reading> readings{{19.5}, {21.0}, {22.5}};
    std::cout << "average " << average(readings) << '\n';
}

— end example]

Give both source files to the compiler, and not the header: cl /nologo /EHsc /W4 /permissive- /utf-8 /std:c++17 main.cpp reading.cpp with MSVC, g++ -std=c++17 main.cpp reading.cpp with GCC (clang++ takes the same arguments). All four printed average 21.

reading.h feeds reading.cpp and main.cpp, each compiled to an object file; one linker joins reading.o and main.o into the program.
Figure 2 — Each source file is compiled alone with its headers pasted in; the linker joins the object files and needs exactly one definition of each ordinary function and variable.

An ordinary function or variable is one that is neither inline nor a template. Build main.cpp alone and the linker finds no definition of average(). The samples recorded that error for highest(), declared in reading.h and defined nowhere; MSVC’s line, then GCC’s:

[Example 4:
error LNK2019: unresolved external symbol
undefined reference to `highest(std::vector<Reading, std::allocator<Reading> > const&)'

— end example]

Add the missing .cpp file to the command, or write the missing body.

What goes in a header, and what goes in the .cpp

What Where Why
Function declaration header every caller needs it
Ordinary function body one .cpp one definition per program
Class or enum definition header the same in every file that uses it
Template, with its body header needed wherever it is used
inline or constexpr function header the same in every file; one function
const or constexpr variable header each file gets its own copy
Variable shared by several files inline in the header (C++17), or extern there and defined in one .cpp one variable for the program

The rule behind the table is the one-definition rule, [basic.def.odr]: a program has exactly one definition of each ordinary function or variable. A class, a template or an inline function is defined in every file that needs it, the same in each. No compiler has to report a mismatch: two files that defined inline double to_display(double) with different bodies linked on all four. Why a template’s body goes in the header has its own answer.

“multiple definition” and LNK2005: a function body in a header

A function body in a header is defined again in every file that includes it, so the program does not link:

[Example 5:
// a definition, not a declaration: every file that includes this
// header now defines average() too
double average(const std::vector<Reading>& readings) {
    // ...
}

— end example]

No compiler warned; the linkers did. MSVC’s reported error LNK2005: ... already defined in main.obj, then the first line below; the GNU linker the second:

[Example 6:
fatal error LNK1169: one or more multiply defined symbols found
multiple definition of `average(std::vector<Reading, std::allocator<Reading> > const&)'

— end example]

Two fixes: move the body to reading.cpp, as in the first example, or mark the function inline; with inline, it ran on all four. A variable defined in a header, int limit = 20;, fails the same way; the table has its fixes.

Include guards and #pragma once

Without a guard, a header reached twice in one file defines its classes twice: main.cpp included reading.h, then sensor.h, which includes it again, and GCC said redefinition of 'struct Reading'. A guard pastes the text once. The #ifndef form:

[Example 7:
#ifndef READING_H
#define READING_H
// ...
#endif

— end example]

#pragma once on the first line, as in reading.h above, does the same; both built on all four. The Core Guidelines’ SF.8, “Use #include guards for all header files”, notes of #pragma once: “It is not standard and it is not portable”; Microsoft writes: “We recommend the #pragma once directive for new code because it doesn’t pollute the global namespace with a preprocessor symbol.”

  • A guard fails when another header reuses its macro, which skips that header, or when its #define misspells it, leaving the header unguarded (GCC and Clang warn at -Wall -Wextra: -Wheader-guard).
  • #pragma once fails when one header exists as two files: a copy of reading.h with one line added, included beside it, redefined Reading on all four.

.h or .hpp, and quotes or angle brackets

The suffix is a convention: #include reads a file of any name, and reading.hpp and sensor.hh built on all four. .hpp says C++ only; .h suits a header that C code includes too (C++ vs C covers extern "C"). “hpp vs cpp” is no choice: .hpp names a header, .cpp a source file.

With GCC and MSVC, quotes search the including file’s folder before the folders angle brackets search: those given with -I or /I, then the system’s. SF.12: quotes for a file found relative to the including file, angle brackets for everything found through an include path.

Include what you use, and break include cycles with a forward declaration

A header includes the header of every name it uses, so it compiles on its own. This reading.h uses std::uint32_t but includes only <vector>:

[Example 8:
#pragma once
#include <vector>  // but not <cstdint>, though std::uint32_t is used

struct Reading {
    std::uint32_t time;  // seconds since the sensor started
    double value;        // degrees Celsius
};

double average(const std::vector<Reading>& readings);

— end example]

Microsoft’s library and libc++ built it, because their <vector> happens to bring std::uint32_t along; libstdc++, GCC’s library, did not: 'uint32_t' in namespace 'std' does not name a type. With #include <cstdint> it built on all four. clang-tidy checks both rules: misc-include-cleaner (clang-tidy 17 and later) reports unused and missing includes in the file it runs on, misc-definitions-in-headers a definition in a header.

A forward declaration, struct Sensor;, declares the name without the class: enough for a pointer, a reference or a function declaration, not for a member of that type or sizeof(Sensor). It also breaks cycles. Here sensor.h included hub.h for Hub*, and hub.h included sensor.h for Sensor*; whichever came first saw the other class missing (GCC: 'Hub' does not name a type). hub.h needs only pointers, so it forward-declares instead:

[Example 9:
#pragma once
#include <vector>

struct Sensor;  // instead of #include "sensor.h": pointers need no more

struct Hub {
    std::vector<Sensor*> sensors;
    void attach(Sensor& sensor);
};

— end example]

hub.cpp, which uses the whole class, includes sensor.h; all four printed 2 sensors on the hub.

Modules, and why headers stay

C++20 modules export declarations without pasting text, and C++23’s import std; imports the whole standard library. With plain flags, import std; failed on all four (GCC: C++20 'import' only available with '-fmodules'): the build must be set up for modules, and CMake 4.4, the current release, still marks import std experimental. Headers remain what most code uses; Since When’s C++20 table has the first compiler releases with modules.

A checklist for every header

  • A guard: #pragma once, or an #ifndef macro no other header uses, spelled the same in its #define.
  • Declarations, classes, templates, and inline, constexpr or const definitions; other function bodies and variables in one .cpp.
  • An include for every name it uses.
  • Quotes for a file found relative to the including file, angle brackets for the rest (SF.12).
  • No using namespace at global scope: using namespace std in a header.
  • A forward declaration where a pointer or a reference is all it needs.
  • Every .cpp file on the build command.

Tooling has the compilers and checks that read these files.

Questions

Should I use .h or .hpp for C++ headers?

Either: #include reads a file of any name as part of the file that includes it, and headers named .hpp and .hh built the same as .h with MSVC, GCC and Clang. Pick one per project, and keep .h for a header that C code includes too.

What is the difference between .hpp and .cpp files?

A .hpp file is a header: declarations, classes and templates that #include pastes into every source file that needs them. A .cpp file is a source file, compiled once, on its own; it holds the bodies of ordinary functions and the definitions of variables. A class usually has one of each.

Should I use include guards or #pragma once?

Either: both stop a header from being pasted twice into one file, and MSVC, GCC and Clang support both. The C++ Core Guidelines (SF.8) ask for #ifndef guards in every header and note that #pragma once is not standard; Microsoft recommends #pragma once for new code. A guard fails when two headers reuse one macro or its #define misspells it; #pragma once fails when one header exists as two files.

Why do I get a “multiple definition” error?

A function or variable is defined, not only declared, in a header that two source files include, so the linker finds two definitions; Microsoft’s linker calls it LNK2005. Move the function body to one .cpp file and keep its declaration in the header, declare a variable extern in the header and define it in one .cpp file, or mark either inline (variables since C++17).

What is a header-only library?

A library shipped as headers alone: its classes and templates, and its functions and variables that are inline or constexpr, may be defined in every file that includes them, so there is no .cpp file to build or link. The price: every translation unit that includes it parses all of it again.

Will C++ modules replace header files?

C++20 modules export declarations without pasting text, and C++23 adds import std for the whole standard library. They need a build set up for them: with plain compiler flags, import std failed on MSVC, GCC and Clang alike, and CMake 4.4, the current release, still marks import std experimental. Headers remain what most code includes.

Sources

  1. [basic.def.odr] One-definition ruleC++ standard working draft
  2. [dcl.inline] The inline specifierC++ standard working draft
  3. Search PathGCC documentation
  4. #include directive (C/C++)Microsoft Learn
  5. SF.8: Use #include guards for all header filesC++ Core Guidelines
  6. once pragmaMicrosoft Learn
  7. misc-include-cleanerLLVM Project
  8. cmake-cxxmodules(7), CMake 4.4CMake documentation

Read next

  1. clang-tidy: run it, configure it, pick the checksTooling · Tobias Mwangi · written for C++17
  2. What does a compiler do? C++ source to programTooling · Tobias Mwangi · written for C++17
  3. What is CMake? A build system generator, explainedTooling · Tobias Mwangi · written for C++17