What You'll Learn
- Optimal include ordering
- Forward declarations to reduce coupling
- PIMPL idiom for compile firewalls
- Self-contained header design
Header File Best Practices
In large C++ projects, compile times are measured in minutes — sometimes hours. The biggest factor? Headers. Every unnecessary #include cascades through the dependency graph, recompiling hundreds of files when one header changes. This lesson teaches the techniques that keep compile times fast and dependencies clean.
Include Order — Catch Bugs Early
Include the corresponding header first (widget.h in widget.cpp). This catches missing includes in the header — if widget.h needs <string> but doesn't include it, putting it first will fail immediately instead of being silently fixed by an earlier include.
Pro Tip: Use clang-format with IncludeBlocks: Regroup and IncludeCategories to automatically sort and group includes on save.
Include Order
Proper header inclusion ordering
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
// === Include Order Best Practices ===
// 1. Corresponding header (e.g., widget.h for widget.cpp)
// 2. C system headers (<cstdio>, <cstring>)
// 3. C++ standard headers (<iostream>, <vector>)
// 4. Third-party library headers (boost, fmt, etc.)
// 5. Your project headers
//
// Each group separated by a blank line
// Alphabetical within each group
// Example for logger.cpp:
// #include "logger.h"
...Forward Declarations — Minimize Dependencies
If a header only uses a pointer or reference to a type, forward-declare it instead of including its header. This breaks the include chain and prevents recompilation when the forward-declared type's implementation changes.
Common Mistake: Forward-declaring standard library types (class string;). This is undefined behaviour — only forward-declare your own types or types from libraries that explicitly support it.
Forward Declarations
Reduce includes by forward-declaring types
#include <iostream>
#include <string>
#include <memory>
using namespace std;
// Forward declarations reduce #include dependencies
// Use when you only need a pointer or reference to a type
// === BAD: unnecessary #include ===
// In renderer.h:
// #include "texture.h" ← pulls in ALL of texture's dependencies
// #include "mesh.h" ← pulls in ALL of mesh's dependencies
// class Renderer {
// Texture* activeTexture; ← only need a pointer!
// Mesh* currentMesh; ← only need a
...PIMPL — The Ultimate Compile Firewall
The PIMPL (Pointer to Implementation) idiom hides all private members behind a forward-declared struct. The header contains only a unique_ptr<Impl>. Changing the implementation recompiles only one .cpp file — not every file that includes the header.
PIMPL Idiom
Hide implementation details behind a pointer
#include <iostream>
#include <memory>
#include <string>
using namespace std;
// PIMPL (Pointer to IMPLementation) idiom
// Hides implementation details from the header
// Reduces compile times and provides ABI stability
// === widget.h (public header) ===
class Widget {
struct Impl; // forward declare
unique_ptr<Impl> pImpl; // only a pointer in header
public:
Widget(const string& name, int value);
~Widget(); // must declare (uniqu
...Quick Reference
| Technique | Compile Time Impact |
|---|---|
| Ordered includes | Catches bugs, no speed change |
| Forward declarations | 10-30% faster in large projects |
| PIMPL | Isolates changes to one .cpp file |
| #pragma once | Prevents redundant parsing |
| Precompiled headers | 50%+ faster for heavy STL usage |
Lesson Complete!
You now know how to organize headers for fast compile times, clean dependencies, and ABI stability.
Sign up for free to track which lessons you've completed and get learning reminders.