C++ Header Files Explained: #include, Guards, and Best Practices
When a C++ program grows beyond one file, you need header files. They’re the glue that lets multiple .cpp files share declarations and work together.
This guide explains what header files are, why they exist, and how to use them correctly.
Why Header Files Exist
Imagine you have two files: math.cpp with a square function, and main.cpp that wants to use it.
// math.cpp
int square(int x) {
return x * x;
}
// main.cpp
#include <iostream>
using namespace std;
// The compiler needs to know square() exists before it can compile this call
int main() {
cout << square(5) << endl; // Error: 'square' was not declared
}
The compiler processes each .cpp file independently. When it compiles main.cpp, it doesn’t know square exists because it hasn’t seen math.cpp yet.
The fix: declare square in a header file:
// math.h — the declaration (the "promise" that this function exists)
int square(int x);
// main.cpp
#include <iostream>
#include "math.h" // Now the compiler knows square() exists
using namespace std;
int main() {
cout << square(5) << endl; // Works!
}
// math.cpp — the definition (the actual implementation)
#include "math.h"
int square(int x) {
return x * x;
}
#include with < > vs " "
#include <iostream> // Angle brackets: standard library / system headers
#include <vector>
#include "math.h" // Quotes: your own project headers
#include "utils.h"
The difference is where the compiler looks:
< >: searches the compiler’s standard include paths first" ": searches the current directory first, then standard paths
Always use quotes for your own headers.
Include Guards
Here’s the problem they solve:
main.cpp includes A.h and B.h
A.h also includes B.h
So main.cpp ends up with B.h included twice → duplicate declarations → error
Include guards prevent this:
// math.h
#ifndef MATH_H // If MATH_H is not defined...
#define MATH_H // ...define it now, and include the contents
int square(int x);
double squareRoot(double x);
#endif // End of the guarded section
The name convention is FILENAME_H in uppercase. The first time math.h is included, MATH_H isn’t defined, so everything between #ifndef and #endif is processed and MATH_H is defined. The second time it’s included, MATH_H is already defined, so everything is skipped.
#pragma once (The Simpler Alternative)
// math.h
#pragma once // Tell the compiler: include this file only once
int square(int x);
double squareRoot(double x);
#pragma once does the same job as include guards but with one line instead of three. It’s supported by all modern compilers (GCC, Clang, MSVC) and is widely used in modern C++ code.
The only downside: it’s not in the official C++ standard (it’s a common extension). Traditional include guards are guaranteed to work everywhere.
What Goes in Header Files vs .cpp Files
In header files (.h):
- Function declarations (signatures without bodies)
- Class definitions
- Struct definitions
- Enum definitions
inlinefunction definitions- Template definitions
constandconstexprvariables- Type aliases (
using/typedef)
In .cpp files:
- Function definitions (implementations)
- Non-inline member function implementations
- Global variable definitions (not declarations)
main()
What NEVER goes in headers:
using namespace std;— pollutes the namespace of every file that includes this header- Non-inline function definitions — would cause “multiple definition” linker errors if included in multiple
.cppfiles - Global variable definitions (without
extern) — same issue
A Complete Multi-File Example
Project structure:
project/
├── main.cpp
├── calculator.h
└── calculator.cpp
// calculator.h
#pragma once
class Calculator {
public:
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
};
// calculator.cpp
#include "calculator.h"
#include <stdexcept>
double Calculator::add(double a, double b) {
return a + b;
}
double Calculator::subtract(double a, double b) {
return a - b;
}
double Calculator::multiply(double a, double b) {
return a * b;
}
double Calculator::divide(double a, double b) {
if (b == 0) throw std::invalid_argument("Division by zero");
return a / b;
}
// main.cpp
#include <iostream>
#include "calculator.h"
using namespace std;
int main() {
Calculator calc;
cout << calc.add(10, 5) << endl; // 15
cout << calc.subtract(10, 5) << endl; // 5
cout << calc.multiply(10, 5) << endl; // 50
cout << calc.divide(10, 5) << endl; // 2
return 0;
}
Compile both .cpp files together:
g++ main.cpp calculator.cpp -o calculator
The Compilation Process
Understanding how headers work requires understanding compilation:
- Preprocessor: Expands all
#includedirectives by literally copying the header file’s contents. Every#includeis replaced with the file contents. - Compiler: Compiles each
.cppfile separately into object files (.o). - Linker: Links all object files together into the final executable, resolving references like “main.cpp said square() exists — find it in math.o”.
Header files are processed in step 1. They’re never compiled independently — only .cpp files are compiled.
Common Mistakes
Defining functions in headers (without inline):
// BAD — if included in two .cpp files, linker gets two definitions of square()
int square(int x) { return x * x; }
// OK — inline tells the compiler to embed it, avoiding duplicate definitions
inline int square(int x) { return x * x; }
Missing include guards:
// BAD — no protection against multiple inclusion
int square(int x);
// GOOD
#pragma once
int square(int x);
Circular includes:
If A.h includes B.h and B.h includes A.h, you have a circular dependency. Fix with forward declarations or restructuring.
Related Articles
- C++ Namespace Tutorial — why
using namespace stdin headers is harmful - C++ Functions Tutorial — declarations vs definitions
- C++ Classes and Objects — class definitions live in headers
- C++ Error Messages Explained — fixing “undefined reference” and “redefinition” errors
Take Your C++ Further
If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.