Lesson 6 / 25

Headers, Source Files, Namespaces and the ODR

Organise code into headers and source files with namespaces, and avoid ODR violations.

Splitting a program across files

C++ code is traditionally split into headers (.h or .hpp) containing declarations (what exists: function signatures, class definitions, templates) and source files (.cpp) containing definitions (the function bodies). Other files #include the header to use the declarations, and the linker connects them to the definitions. Protect headers from being included twice in one translation unit with include guards or the widely supported #pragma once. The One Definition Rule (ODR) says each non-inline function or variable must be defined exactly once in the whole program; defining a normal function in a header included by two .cpp files causes a "multiple definition" link error. Templates, inline functions, constexpr functions and class definitions may appear in headers because they are allowed in multiple translation units if identical. Namespaces (namespace shop { ... }) group related names and prevent clashes; avoid using namespace std; in headers, where it leaks into every file that includes them. C++20 modules (export module geometry;) aim to replace headers with faster, cleaner imports, and compiler support has been improving steadily.

A header, its source file and a user

Declarations in the header, definitions in one .cpp file.

// geometry.hpp
#pragma once
#include <string>

namespace geo {
    struct Point { double x{}; double y{}; };
    double distance(const Point& a, const Point& b);    // declaration
    std::string to_string(const Point& p);
    inline double square(double v) { return v * v; }     // inline: OK in a header
}

// geometry.cpp
#include "geometry.hpp"
#include <cmath>
#include <format>

namespace geo {
    double distance(const Point& a, const Point& b) {      // the one definition
        return std::sqrt(square(a.x - b.x) + square(a.y - b.y));
    }
    std::string to_string(const Point& p) { return std::format("({}, {})", p.x, p.y); }
}

// main.cpp
#include "geometry.hpp"
#include <print>
int main() {
    geo::Point a{0, 0}, b{3, 4};
    std::print("{} to {} is {}\n", geo::to_string(a), geo::to_string(b), geo::distance(a, b));
}

Never put using namespace std in a header

It injects every standard name into every file that includes your header, causing confusing ambiguities with names like count, distance or size. In source files, prefer specific using std::string; declarations or just write std::.

Quick check: Why does defining a non-inline function in a header included by two source files cause a link error?

  • It violates the One Definition Rule: the function is defined twice in the program
  • Headers cannot contain functions
  • The compiler ignores headers
  • Namespaces are missing
Answer

It violates the One Definition Rule: the function is defined twice in the program — Each translation unit gets its own definition, so the linker finds duplicate symbols.