Real C++ programs are rarely one file. Headers declare what other translation units may use; source files define behavior. Getting that boundary wrong produces duplicate-symbol linker errors, subtle ABI breaks, or headers that silently inject names into every includer.
What belongs in a header
A header should expose a stable interface: type declarations, function declarations, inline definitions, templates, and constexpr entities that must be visible at the point of use. It should not hide large implementation details that change often unless you are deliberately publishing a small facade.
Use include guards or #pragma once so the preprocessor does not paste the same text twice into one translation unit. That prevents redefinition errors within one .cpp file, but it does not solve multiple definitions across the whole program.
The One Definition Rule (ODR)
The ODR says that non-inline functions, non-inline variables, and most class definitions may appear at most once in the entire program. Put ordinary definitions in exactly one .cpp file. Put declarations in headers when other files need the name.
Templates, inline functions, and inline variables are exceptions: their definitions usually live in headers and must be token-identical in every translation unit that uses them.
Common ODR mistakes
* Definition in a header without inline: every .cpp that includes the header emits a copy; the linker reports multiple definition of ....
* Different definitions in different files: even if each file compiles, linking identical symbols with different bodies is undefined behavior.
* Non-inline variables in headers: same duplicate-symbol failure as functions.
Fix: move the body to one .cpp, or mark the entity inline when the language allows header definitions.
Linkage: who can see the name?
Internal linkage (static at namespace scope, or unnamed namespaces) keeps a name local to one translation unit. External linkage lets other translation units refer to the symbol through declarations.
Static libraries, shared libraries, and ABI
A static library (.a, .lib) archives object files; the linker copies needed symbols into your executable. A shared library (.so, .dll) is loaded at runtime and can be updated independently, but you must respect ABI compatibility: compiler, standard library, and flags must match what the library was built with unless the vendor documents a stable ABI.
Treat a dependency's documented toolchain as part of its API. Mixing MSVC-built and MinGW-built C++ across a DLL boundary without a C interface is a common source of mysterious crashes.
Typical linker errors and what they mean
* undefined reference to ...: a declaration was used but no translation unit provided a matching definition (missing .cpp in the build, wrong signature, or forgot to link a library).
* multiple definition of ...: more than one definition reached the linker, almost always a header definition mistake.
* duplicate symbol in static data: same as multiple definition for variables.
Lesson 0.3 showed the compile pipeline; this lesson is the multi-file half of that story.
Modules vs headers (preview)
C++20 modules can reduce macro leakage and improve build times, but headers remain everywhere in production codebases. Lesson 8.7 introduces modules; until then, master headers, ODR, and CMake targets from Lessons 0.4 and 8.8.
Practice: split a one-file program into main.cpp, a header, and one library source file. Introduce one intentional ODR violation, read the linker error, then fix it.