Headers and Multiple Source Files
Real C++ programs are split across many files. A header (.h or .hpp) declares classes and functions; a source file (.cpp) defines them. Each source file is compiled separately, and the linker joins the results. This lesson builds a small multi-file program and explains the rules that make it work.
The layout
inventory/ ├── product.hpp ← the interface: the class and its member declarations ├── product.cpp ← the implementation └── main.cpp ← the program
#pragma once #include <string> class Product { public: Product(std::string name, double price, int stock); const std::string& name() const { return name_; } // short functions can live in the header double stock_value() const; void restock(int amount); private: std::string name_; double price_; int stock_; }; std::string format_money(double amount);
#include "product.hpp" #include <format> #include <stdexcept> #include <utility> Product::Product(std::string name, double price, int stock) : name_(std::move(name)), price_(price), stock_(stock) { if (price < 0 || stock < 0) throw std::invalid_argument("negative price or stock"); } double Product::stock_value() const { return price_ * stock_; } void Product::restock(int amount) { if (amount > 0) stock_ += amount; } std::string format_money(double amount) { return std::format("{:.2f}", amount); }
#include <iostream> #include <vector> #include "product.hpp" int main() { std::vector<Product> shelf{{"Notebook", 3.50, 40}, {"Pen", 1.20, 150}}; shelf[0].restock(10); double total = 0; for (const auto& p : shelf) { std::cout << p.name() << ": " << format_money(p.stock_value()) << '\n'; total += p.stock_value(); } std::cout << "total: " << format_money(total) << '\n'; }
Notebook: 175.00 Pen: 180.00 total: 355.00
Building
g++ -std=c++20 -Wall -Wextra main.cpp product.cpp -o inventory
Or compile each file to an object file and link them, so a change to one file only recompiles that file:
g++ -std=c++20 -Wall -Wextra -c product.cpp # product.o g++ -std=c++20 -Wall -Wextra -c main.cpp # main.o g++ main.o product.o -o inventory # link
Build tools such as CMake (next lesson) do this for you.
Include guards
A header may end up included twice in one source file (directly and through another header). #pragma once at the top makes the second inclusion a no-op. It's supported by every major compiler; the traditional, fully standard alternative is an include guard:
#ifndef PRODUCT_HPP #define PRODUCT_HPP // ... declarations ... #endif
The one-definition rule
Each function and variable may be declared many times but must be defined exactly once in the whole program — otherwise the linker reports duplicate symbols. That's why headers normally contain only declarations. The exceptions, which may appear in every file that includes the header:
- Class definitions, and member functions defined inside the class body.
- Templates (lessons 39–40) — their full definitions must be visible.
- Functions and variables marked
inline, andconstexprfunctions.
// in a header: fine, because it's inline inline double vat(double net) { return net * 0.2; } inline constexpr double vat_rate = 0.2;
Internal linkage
Helpers that only one source file needs should be invisible to the rest of the program. Put them in an anonymous namespace (or mark them static):
namespace { double round_cents(double x) { return static_cast<long long>(x * 100 + 0.5) / 100.0; } } double total_with_rounding(double a, double b) { return round_cents(a + b); }
Header hygiene
- Each header includes what it needs, so it compiles on its own.
- A source file includes its own header first — that checks the header is self-contained.
- Never put
using namespacein a header; it leaks into every file that includes it. - Prefer forward declarations (
class Product;) in headers when only a pointer or reference is needed, to keep compile times down.
Modules
C++20 introduced modules, a replacement for headers: export module inventory; in one file and import inventory; in another. Modules compile faster and don't leak macros, and C++23 adds import std; for the whole standard library. Compiler and build-tool support has matured (CMake 3.28+ supports them with recent compilers), but headers remain the norm in most existing projects.
Try it yourself
Split a small program of your own into a header, an implementation file and main.cpp, and build it with a single g++ command. This version keeps everything in one file to start from:
Welcome, Sam!