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

TXT
inventory/
├── product.hpp      ← the interface: the class and its member declarations
├── product.cpp      ← the implementation
└── main.cpp         ← the program
HPP product.hpp
#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);
C++ product.cpp
#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);
}
C++ main.cpp
#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';
}
Output
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:

HPP
#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, and constexpr functions.
HPP
// 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):

C++
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 namespace in 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:

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
Welcome, Sam!