Every multi-file C++ project depends on this structure, and most linker errors come from getting it wrong.
C++ code is split into headers (.h/.hpp), which contain declarations other files need, and source files (.cpp), which contain definitions. A source file #includes the headers it uses; the preprocessor pastes them in textually. Each header starts with an include guard, #pragma once (supported everywhere) or the classic #ifndef X_H / #define X_H / #endif, so it isn't processed twice in one translation unit.
The one-definition rule (ODR): a non-inline function or variable may be defined only once in the whole program. If you put int helper() { ... } in a header included by two .cpp files, the linker reports "multiple definition". Things that may appear in headers, because they're implicitly or explicitly inline, are class definitions, templates, inline functions, constexpr functions, member functions defined inside a class body, and C++17 inline variables. Their definitions must be identical everywhere; violating that is undefined behaviour with no diagnostic.
Namespaces group names and prevent clashes: namespace geo { ... }, used as geo::distance. C++17 lets you write nested ones as namespace app::net { }. Never write using namespace in a header, because it leaks into every file that includes it. In a .cpp file, a targeted using std::string; is fine.
An unnamed namespace, namespace { ... }, gives its contents internal linkage: visible only inside that .cpp. Use it for file-local helpers (the modern replacement for static functions).
C++20 modules (export module geo; / import geo;) and C++23's import std; aim to replace headers with faster, isolated compilation. Compiler and build-tool support is improving, but headers remain the norm.
Example
// ---- geometry.h ----
#pragma once // process at most once per translation unit
namespace geo {
struct Point { double x, y; };
double distance(Point a, Point b); // declaration only
inline constexpr double pi = 3.14159265358979; // inline: one entity in all TUs
} // namespace geo
// ---- geometry.cpp ----
#include "geometry.h"
#include <cmath>
namespace { // internal linkage: only this file sees it
double square(double v) { return v * v; }
}
double geo::distance(Point a, Point b) {
return std::sqrt(square(a.x - b.x) + square(a.y - b.y));
}
// ---- main.cpp ---- build: g++ -std=c++20 main.cpp geometry.cpp -o app
#include "geometry.h"
#include <iostream>
int main() { std::cout << geo::distance({0, 0}, {3, 4}) << '\n'; } // 5Remember
- Headers declare, source files define; guard every header with
#pragma once - Only inline things (classes, templates,
inline/constexprfunctions) may be defined in headers - Use an unnamed namespace for file-local helpers; never
using namespacein a header
Watch out for
- Defining a normal function in a header and getting "multiple definition" link errors once two files include it.