C++ / NAMESPACES, HEADERS, AND BUILDS
The one-definition rule and linker errors
Diagnose undefined reference and multiple definition errors, and fix them with inline, extern, or one definition in a single .cpp file.
What you will learn
- Read undefined reference as: a symbol was used but no object file defined it.
- Mark definitions that live in headers inline so every translation unit may repeat them.
- Define class static data members out of class, or declare them static inline.
- Spot silent ODR breaks: mismatched struct or inline bodies across TUs, one wins.
Understanding The one-definition rule and linker errors
A C++ build has two stages that check different things. The compiler processes one translation unit at a time, meaning a single .cpp plus everything it includes, and it only needs declarations: `int add(int, int);` is enough to type-check the call and emit a reference to a symbol. The linker then collects every object file and must match each outstanding reference to exactly one definition. `undefined reference to add(int, int)` means the reference survived compilation but no object file supplied a body; `multiple definition of add(int, int)` means two object files supplied one.
The one-definition rule sets the budget. Non-inline functions and variables with external linkage get one definition in the whole program, and that definition is only required if the entity is actually used, which is why declaring a function nobody calls links fine. Classes, enums, templates, inline functions, and inline variables get a different budget of one definition per translation unit, because their definitions have to be visible in every file that uses them. That is why headers have the shape they do: declarations and inline-eligible definitions go in the header, everything else goes in exactly one .cpp.
The second half of the rule is where the tools stop helping. When an inline function or a class is defined in several translation units, all those definitions must consist of the same tokens and mean the same thing; if they do not, the program is ill-formed but no diagnostic is required. In practice the linker sees several weak symbols with one name, keeps one, and drops the rest, so a struct whose member list changed under an `#ifdef` in one .cpp builds cleanly and then reads fields at the wrong offsets. Treat a multiple-definition error as information about your header layout rather than noise to silence, because the invisible version of the same mistake is far more expensive to debug.
<iostream>
// Declaration only: it promises a definition exists somewhere in the program.
// Repeating a declaration is always legal.
int mystery(int);
int mystery(int);
// A non-inline function definition. This body may appear once in the entire
// program, no matter how many .cpp files there are.
int square(int x) { return x * x; }
// inline changes the budget to one definition per translation unit, which is
// what makes a body safe to put in a header that many .cpp files include.
inline int cube(int x) { return x * square(x); }
// inline variable (C++17): every TU may define it, and all TUs share one object.
inline int callCount = 0;
int main() {
++callCount;
++callCount;
std::cout << "square(4) = " << square(4) << '\n';
std::cout << "cube(3) = " << cube(3) << '\n';
std::cout << "callCount = " << callCount << '\n';
// mystery has no definition anywhere, and that is fine until it is used.
// Uncommenting the next line turns this into a link error:
// undefined reference to mystery(int)
// std::cout << mystery(1) << '\n';
std::cout << "unused declarations never reach the linker\n";
}
Declarations satisfy the compiler and definitions satisfy the linker: each entity needs exactly one definition per program, or one per translation unit when it is inline, a template, or a class.
Worked examples
Static data members need a definition
Shows why a class static member declared in a header produces an undefined reference unless it is defined once or marked inline.
<iostream>
struct Counter {
static int total; // declaration: allocates nothing
static inline int alive = 0; // C++17: declaration and definition at once
Counter() { ++total; ++alive; }
};
int Counter::total = 0; // the one definition, at namespace scope
int main() {
Counter a, b, c;
std::cout << "total = " << Counter::total << '\n';
std::cout << "alive = " << Counter::alive << '\n';
}
Example explained
Line 1`static int total;` inside the class only declares the member, so no storage exists yet.
Line 2`int Counter::total = 0;` is the definition; remove that line and the file still compiles but the link fails with undefined reference to Counter::total.
Line 3`static inline int alive = 0;` is declaration and definition together, and being inline it may be repeated in every TU that includes the class.
Line 4Both counters read 3 because all three constructor calls increment the same single object in each case.
Templates and explicit instantiation
Demonstrates that template definitions are exempt from the one-per-program limit, and how explicit instantiation lets a body stay in one .cpp.
<iostream>
// A template definition may appear in every translation unit that uses it,
// so it normally belongs in the header beside the declaration.
template <typename T>
T maxOf(T a, T b) { return a < b ? b : a; }
// Explicit instantiation: emits one copy of maxOf<long> in this TU, even if
// no code here needed it, so other TUs can link with only a declaration.
template long maxOf<long>(long, long);
int main() {
std::cout << maxOf(3, 9) << '\n';
std::cout << maxOf(2.5, 1.5) << '\n';
std::cout << maxOf<long>(7, 4) << '\n';
}
Example explained
Line 1Every TU that calls maxOf<int> generates its own copy of the body, and the linker folds the copies instead of reporting multiple definition.
Line 2Hiding this body in a .cpp is the classic source of undefined reference to maxOf<int>(int, int), because no instantiation ever happens where the body is visible.
Line 3`template long maxOf<long>(long, long);` instantiates the long version in the TU that owns the body, which is the escape hatch when you insist on keeping templates out of headers.
Line 4`maxOf(2.5, 1.5)` prints 2.5 because deduction gives T = double and the comparison returns the larger argument.
const, extern, and linkage in headers
Shows why a const variable can be defined in a header safely while a non-const or extern one needs exactly one definition.
<iostream>
const int kRows = 3; // const at namespace scope: internal linkage
extern const int kCols; // opts into external linkage: needs one definition
const int kCols = 4; // ...and this is that definition
inline const char* gridName() { return "board"; }
int main() {
std::cout << gridName() << ' ' << kRows << 'x' << kCols << '\n';
std::cout << "cells: " << kRows * kCols << '\n';
}
Example explained
Line 1`const int kRows = 3;` has internal linkage, so a header may define it in every TU without a collision, at the cost of a separate object with its own address per TU.
Line 2`extern const int kCols;` gives the name external linkage, putting it back under the one-definition-per-program rule; delete the next line and the link fails.
Line 3`inline const char* gridName()` needs inline for the same reason: without it, a second .cpp including this header collides at link time.
Line 4Nothing here is a compile error in either case, which is why these bugs surface only when a second file joins the build.
Important notes
inline on a function is a rule about linkage and duplicate definitions, not an order to expand the call site; the optimizer decides expansion independently.
inline variables and static inline data members require C++17; before that, a header-only counter needed a definition in exactly one .cpp or a function returning a function-local static.
Common mistakes
Putting a free function body in a header without inline. The first .cpp compiles and links, and the failure only appears when a second .cpp includes the header, as multiple definition of foo() reported against a header line with no compiler error at all.
Silencing that error with static in the header. The link succeeds, but each TU now has a private copy, so a counter or cache in the header stops accumulating and pointers to it compare unequal across files.
Declaring a template in the header and defining it in a .cpp. Other files get undefined reference to the mangled instantiation, such as maxOf<int>(int, int), because instantiation only happens where the body is visible.
Try it yourself
Change, predict, then run
In one file, write `struct Registry { static int count; Registry() { ++count; } };`, create two objects, and print Registry::count with no out-of-class definition so you can read the exact linker error. Then fix it twice, once by adding `int Registry::count = 0;` and once by changing the member to `static inline int count = 0;`, checking that the printed value is the same.
Open the C++ workspaceCheck your understanding
A header defines `int nextId() { static int n = 0; return ++n; }` with no inline keyword, and two .cpp files include that header. What happens when you build?
- The compiler rejects the second .cpp with a redefinition error.
- Both files compile, and the linker reports multiple definition of nextId().
- It builds and works, because the static local variable already makes the function per-file.
- It builds, and the two files each get their own copy of n.
Show answer
Each .cpp is compiled separately, so no compiler run ever sees the other copy of the body; the duplicate only becomes visible when the linker merges object files, and nextId has external linkage, so two definitions collide. Option 3 describes what would happen if you wrote static int nextId(), which gives the function internal linkage and a private n per TU; the static on the local variable controls storage duration only and does nothing about linkage. Marking the function inline is the fix that keeps one shared n.